Commit 0b86d77e authored by Romain Courteaud's avatar Romain Courteaud

erp5_document_scanner: fully erase the gadget DOM when changing its state

Stop relying of CSS hide/show attributes.
Instead, only put needed elements in the DOM.
parent 73377c72
...@@ -7,7 +7,7 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] { ...@@ -7,7 +7,7 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] {
text-align: center; text-align: center;
} }
.video, .photo, .camera-output { .video, .photo, .camera-output, .canvas {
max-width: 100%; max-width: 100%;
width: auto; width: auto;
max-height: 500px; max-height: 500px;
...@@ -17,11 +17,11 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] { ...@@ -17,11 +17,11 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] {
.camera-input, .camera-output { .camera-input, .camera-output {
min-height: 360px; min-height: 360px;
display: none; // display: none;
} }
.canvas { .canvas {
display: none; // display: none;
filter: brightness(1); filter: brightness(1);
} }
...@@ -58,12 +58,20 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] { ...@@ -58,12 +58,20 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] {
padding: 3pt; padding: 3pt;
border: 1px solid rgba(0, 0, 0, 0.14); border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em; border-radius: 0.325em;
display: "inline-block";
margin-right: 6pt;
}
.ui-btn-icon-left:before {
margin-right: 6pt;
} }
/*
.take-picture-btn, .capture-btn, .confirm-btn, .take-picture-btn, .capture-btn, .confirm-btn,
.reset-btn, .confirm-btn, .change-camera-btn, .edit-btn { .reset-btn, .confirm-btn, .change-camera-btn, .edit-btn {
display: none; display: none;
} }
*/
.contentarea { .contentarea {
font-size: 16px; font-size: 16px;
......
...@@ -69,7 +69,9 @@ ...@@ -69,7 +69,9 @@
</item> </item>
<item> <item>
<key> <string>content_type</string> </key> <key> <string>content_type</string> </key>
<value> <string>text/css</string> </value> <value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>default_reference</string> </key> <key> <string>default_reference</string> </key>
...@@ -167,9 +169,7 @@ ...@@ -167,9 +169,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>action</string> </key> <key> <string>action</string> </key>
<value> <value> <string>publish_alive</string> </value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>actor</string> </key> <key> <string>actor</string> </key>
...@@ -188,50 +188,11 @@ ...@@ -188,50 +188,11 @@
<value> <value>
<object> <object>
<klass> <klass>
<global id="3.1" name="DateTime" module="DateTime.DateTime"/> <global name="DateTime" module="DateTime.DateTime"/>
</klass> </klass>
<tuple> <tuple>
<none/> <none/>
</tuple> </tuple>
<state>
<tuple>
<float>1574261202.27</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>draft</string> </value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="3.1"/> </klass>
<tuple>
<none/>
</tuple>
<state> <state>
<tuple> <tuple>
<float>1574261323.29</float> <float>1574261323.29</float>
...@@ -262,51 +223,6 @@ ...@@ -262,51 +223,6 @@
<key> <string>_log</string> </key> <key> <string>_log</string> </key>
<value> <value>
<list> <list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global id="4.1" name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261202.26</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary> <dictionary>
<item> <item>
<key> <string>action</string> </key> <key> <string>action</string> </key>
...@@ -328,7 +244,7 @@ ...@@ -328,7 +244,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>979.60822.46188.23978</string> </value> <value> <string>981.19198.64390.32238</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -338,148 +254,15 @@ ...@@ -338,148 +254,15 @@
<key> <string>time</string> </key> <key> <string>time</string> </key>
<value> <value>
<object> <object>
<klass> <reference id="4.1"/> </klass> <klass>
<tuple> <global name="DateTime" module="DateTime.DateTime"/>
<none/> </klass>
</tuple>
<state>
<tuple>
<float>1574261231.12</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60823.12148.22101</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261265.43</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2211.58828.61730</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574688148.62</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2402.31270.49459</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple> <tuple>
<none/> <none/>
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1574697983.15</float> <float>1579541862.76</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -14,11 +14,13 @@ ...@@ -14,11 +14,13 @@
<link rel="stylesheet" href="gadget_document_scanner.css"> <link rel="stylesheet" href="gadget_document_scanner.css">
<link rel="stylesheet" href="cropper.min.css"> <link rel="stylesheet" href="cropper.min.css">
<script type="text/javascript" src="cropper.min.js"></script> <script type="text/javascript" src="cropper.min.js"></script>
<script type="text/javascript" src="domsugar.js"></script>
<script type="text/javascript" src="gadget_document_scanner.js"></script> <script type="text/javascript" src="gadget_document_scanner.js"></script>
<title>Gadget Document Scanner</title> <title>Gadget Document Scanner</title>
</head> </head>
<body> <body>
<div class="camera"> <div></div>
<!--div class="camera">
<div class="camera-header"> <div class="camera-header">
<h4>Page <label class="page-number">1</label></h4> <h4>Page <label class="page-number">1</label></h4>
</div> </div>
...@@ -38,6 +40,6 @@ ...@@ -38,6 +40,6 @@
<button type="button" class="edit-btn ui-btn-icon-left ui-icon-pencil"> Edit</button> <button type="button" class="edit-btn ui-btn-icon-left ui-icon-pencil"> Edit</button>
<button type="button" class="change-camera-btn ui-icon-refresh ui-btn-icon-left"> Change Camera</button> <button type="button" class="change-camera-btn ui-icon-refresh ui-btn-icon-left"> Change Camera</button>
</div> </div>
</div> </div-->
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -167,9 +167,7 @@ ...@@ -167,9 +167,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>action</string> </key> <key> <string>action</string> </key>
<value> <value> <string>publish_alive</string> </value>
<none/>
</value>
</item> </item>
<item> <item>
<key> <string>actor</string> </key> <key> <string>actor</string> </key>
...@@ -188,50 +186,11 @@ ...@@ -188,50 +186,11 @@
<value> <value>
<object> <object>
<klass> <klass>
<global id="3.1" name="DateTime" module="DateTime.DateTime"/> <global name="DateTime" module="DateTime.DateTime"/>
</klass> </klass>
<tuple> <tuple>
<none/> <none/>
</tuple> </tuple>
<state>
<tuple>
<float>1574261595.83</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>draft</string> </value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="3.1"/> </klass>
<tuple>
<none/>
</tuple>
<state> <state>
<tuple> <tuple>
<float>1574261995.43</float> <float>1574261995.43</float>
...@@ -262,96 +221,6 @@ ...@@ -262,96 +221,6 @@
<key> <string>_log</string> </key> <key> <string>_log</string> </key>
<value> <value>
<list> <list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global id="4.1" name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261595.83</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60829.17295.36334</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261616.19</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary> <dictionary>
<item> <item>
<key> <string>action</string> </key> <key> <string>action</string> </key>
...@@ -373,7 +242,7 @@ ...@@ -373,7 +242,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>979.60829.39532.12458</string> </value> <value> <string>981.18960.58948.16110</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -383,373 +252,15 @@ ...@@ -383,373 +252,15 @@
<key> <string>time</string> </key> <key> <string>time</string> </key>
<value> <value>
<object> <object>
<klass> <reference id="4.1"/> </klass> <klass>
<tuple> <global name="DateTime" module="DateTime.DateTime"/>
<none/> </klass>
</tuple>
<state>
<tuple>
<float>1574261638.39</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60835.60550.28808</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574262871.91</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60850.34860.27477</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574263390.85</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2211.58828.61730</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574679628.14</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2260.30741.13158</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574679704.62</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2261.48739.22886</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574686933.44</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2382.14691.53162</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574687074.8</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2384.38015.42888</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574688159.56</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2402.43212.46062</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple> <tuple>
<none/> <none/>
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1574698038.29</float> <float>1579527559.65</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
/*jslint indent: 2 */ /*jslint indent: 2, unparam: true */
/*global rJS, RSVP, window, navigator, Cropper, Promise, JSON, jIO*/ /*global rJS, RSVP, window, document, navigator, Cropper, Promise, JSON, jIO, promiseEventListener, domsugar, createImageBitmap*/
(function (rJS, RSVP, window, navigator, Cropper, Promise, JSON, jIO) { (function (rJS, RSVP, window, document, navigator, Cropper, Promise, JSON, jIO, promiseEventListener, domsugar, createImageBitmap) {
"use strict"; "use strict";
function drawCanvas(gadget, img) { //////////////////////////////////////////////////
var ratio, x, y, // Browser API to promise
root = gadget.element, //////////////////////////////////////////////////
canvas = root.querySelector("canvas"); function promiseUserMedia(device_id) {
canvas.width = gadget.props.image_width; return navigator.mediaDevices.getUserMedia({
canvas.height = gadget.props.image_height; video: {
ratio = Math.min(canvas.width / img.width, canvas.height / img.height); deviceId: {
x = (canvas.width - img.width * ratio) / 2; exact: device_id
y = (canvas.height - img.height * ratio) / 2; }
},
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); audio: false
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height, x, y, img.width * ratio, img.height * ratio);
//contrastImage(canvas, canvas, 10);
root.querySelector(".camera-output").style.display = "";
if (gadget.props.cropper) {
gadget.props.cropper.destroy();
}
// creating Cropper is asynchronous
return new RSVP.Promise(function (resolve) {
gadget.props.cropper = new Cropper(root.querySelector('.photo'), {
data: gadget.props.preferred_cropped_canvas_data,
ready: resolve
});
}); });
} }
function takePicture(gadget) { function handleUserMedia(device_id, callback) {
var el = gadget.element, // Do not modify this function!
image_capture = gadget.props.image_capture; // There is no need to add the gadget logic inside
return new RSVP.Queue() var stream;
.push(function () {
return image_capture.takePhoto({imageWidth: gadget.props.image_width});
})
.push(function (blob) {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (result) {
var photoInput = el.querySelector(".photoInput"),
photo = el.querySelector("img"),
data_str = result.target.result;
photo.setAttribute("src", data_str);
photoInput.setAttribute("value", data_str.split(",")[1]);
return drawCanvas(gadget, photo);
});
}
function enableButton(root) { function canceller() {
[".reset-btn", ".take-picture-btn", if (stream !== undefined) {
".confirm-btn", ".change-camera-btn"].forEach(function (e) { // Stop the streams
root.querySelector(e).disabled = false; stream.getTracks().forEach(function (track) {
}); track.stop();
} });
}
}
function setPageOne(gadget) { function waitForStream(resolve, reject) {
var root = gadget.element; new RSVP.Queue()
root.querySelector(".page-number").innerText = gadget.props.page_number; .push(function () {
root.querySelector(".reset-btn").style.display = "none"; return promiseUserMedia(device_id);
root.querySelector(".take-picture-btn").style.display = "inline-block"; })
root.querySelector(".confirm-btn").style.display = "none"; .push(function (result) {
root.querySelector(".camera-input").style.display = ""; stream = result;
if (gadget.props.camera_list.length > 1) { return callback(stream);
root.querySelector(".change-camera-btn").style.display = "inline-block"; })
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
}
});
} }
return enableButton(root); return new RSVP.Promise(waitForStream, canceller);
} }
function setPageTwo(root) { function handleCropper(element, data, callback) {
root.querySelector(".reset-btn").style.display = "inline-block"; var cropper;
root.querySelector(".confirm-btn").style.display = "inline-block";
root.querySelector(".take-picture-btn").style.display = "none";
root.querySelector(".camera-input").style.display = "none";
root.querySelector(".camera-output").style.display = "";
root.querySelector(".change-camera-btn").style.display = "none";
}
function disableButton(root) { function canceller() {
[".reset-btn", ".take-picture-btn", cropper.destroy();
".confirm-btn", ".change-camera-btn"].forEach(function (e) { }
root.querySelector(e).disabled = true;
}); // creating Cropper is asynchronous
return new RSVP.Promise(function (resolve, reject) {
cropper = new Cropper(element, {
data: data,
ready: function () {
return new RSVP.Queue()
.push(function () {
return callback(cropper);
})
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
}
});
}
});
}, canceller);
} }
//////////////////////////////////////////////////
// helper function
//////////////////////////////////////////////////
/*function contrastImage(input, output, contrast) { /*function contrastImage(input, output, contrast) {
var i, var i,
outputContext, outputContext,
...@@ -95,7 +87,6 @@ ...@@ -95,7 +87,6 @@
imageData = inputContext.getImageData(0, 0, input.width, input.height), imageData = inputContext.getImageData(0, 0, input.width, input.height),
data = imageData.data, data = imageData.data,
factor = (259 * (contrast + 255)) / (255 * (259 - contrast)); factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (i = 0; i < data.length; i += 4) { for (i = 0; i < data.length; i += 4) {
data[i] = factor * (data[i] - 128) + 128; data[i] = factor * (data[i] - 128) + 128;
data[i + 1] = factor * (data[i + 1] - 128) + 128; data[i + 1] = factor * (data[i + 1] - 128) + 128;
...@@ -114,7 +105,6 @@ ...@@ -114,7 +105,6 @@
imageData = inputContext.getImageData(0, 0, input.width, input.height), imageData = inputContext.getImageData(0, 0, input.width, input.height),
data = imageData.data, data = imageData.data,
arraylength = input.width * input.height * 4; arraylength = input.width * input.height * 4;
//gray = 0.3*R + 0.59*G + 0.11*B //gray = 0.3*R + 0.59*G + 0.11*B
// http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/ // http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/
for (i = arraylength - 1; i > 0; i -= 4) { for (i = arraylength - 1; i > 0; i -= 4) {
...@@ -125,7 +115,6 @@ ...@@ -125,7 +115,6 @@
} }
outputContext = outputCanvas.getContext("2d"); outputContext = outputCanvas.getContext("2d");
outputContext.putImageData(imageData, 0, 0); outputContext.putImageData(imageData, 0, 0);
data = canvas.toDataURL("image/png"); data = canvas.toDataURL("image/png");
output.setAttribute("src", data); output.setAttribute("src", data);
if (cropper) { if (cropper) {
...@@ -142,203 +131,331 @@ ...@@ -142,203 +131,331 @@
}); });
}*/ }*/
function handleUserMedia(gadget, callback) { function getVideoDeviceList() {
var stream, if (!navigator.mediaDevices) {
video = gadget.props.video; throw new Error("mediaDevices is not supported");
}
video.autoplay = "autoplay"; return new RSVP.Queue()
.push(function () {
return navigator.mediaDevices.enumerateDevices();
})
.push(function (info_list) {
var j,
device,
len = info_list.length,
device_list = [];
function canceller() { for (j = len - 1; j >= 0; j -= 1) {
if (stream !== undefined) { // trick to select back camera in mobile
// Stop the streams device = info_list[j];
stream.getTracks().forEach(function (track) { if (device.kind === 'videoinput') {
track.stop(); device_list.push(device);
}); }
} }
} return device_list;
});
}
function waitForStream() { function selectMediaDevice(current_device_id, force_new_device) {
return new RSVP.Queue() return getVideoDeviceList()
.push(function () { .push(function (info_list) {
return navigator.mediaDevices.getUserMedia({ var j,
video: { device,
deviceId: { len = info_list.length;
exact: gadget.props.device_id
} for (j = len - 1; j >= 0; j -= 1) {
// trick to select back camera in mobile
device = info_list[j];
if (device.kind === 'videoinput') {
if ((!current_device_id) ||
(force_new_device && (device.deviceId !== current_device_id)) ||
(!force_new_device && (device.deviceId === current_device_id))) {
return device.deviceId;
} }
});
})
.push(function (mediaStream) {
stream = mediaStream;
video.srcObject = mediaStream;
return callback(gadget, stream);
})
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
} }
}); }
throw new Error("no media found");
});
}
//////////////////////////////////////////////////
// Private gadget function
//////////////////////////////////////////////////
function addDetachedPromise(gadget, key, promise) {
// XXX TODO Handle error
if (gadget.detached_promise_dict.hasOwnProperty(key)) {
gadget.detached_promise_dict[key].cancel('Replacing key: ' + key);
} }
gadget.detached_promise_dict[key] = new RSVP.Queue()
.push(function () {
return promise;
})
.push(undefined, function (error) {
// Crash the gadget if the detached promise raise an unexpected error
gadget.raise(error);
});
}
return new RSVP.Promise(waitForStream, canceller); // Display the video stream from a media source
function renderVideoCapture(gadget) {
var video;
return RSVP.Queue()
.push(function () {
var defer = RSVP.defer();
addDetachedPromise(gadget, 'media_stream',
handleUserMedia(gadget.state.device_id, defer.resolve));
return defer.promise;
})
.push(function (media_stream) {
video = document.createElement('video');
video.srcObject = media_stream;
video.autoplay = "autoplay";
video.loop = "loop";
video.muted = "muted";
return RSVP.any([
// Wait for the video to be ready
promiseEventListener(video, 'loadedmetadata', true),
promiseEventListener(video, 'canplaythrough', true),
promiseEventListener(video, 'error', true, function () {
throw new Error("Can't play the video file");
})
]);
})
.push(function () {
video.play();
return RSVP.all([
getVideoDeviceList(),
gadget.getTranslationList(["Take Picture", "Change Camera"])
]);
})
.push(function (result_list) {
var button_list = [
domsugar('button', {type: 'button',
class: 'take-picture-btn ui-btn-icon-left ui-icon-circle',
text: result_list[1][0]
})
],
div;
// Only display the change camera if device has at least 2 cameras
if (result_list[0].length > 1) {
button_list.push(
domsugar('button', {type: 'button',
class: 'change-camera-btn ui-icon-refresh ui-btn-icon-left',
text: result_list[1][1]
})
);
}
div = domsugar('div', {class: 'camera'}, [
domsugar('div', {class: 'camera-header'}, [
domsugar('h4', [
'Page ',
domsugar('label', {class: 'page-number', text: gadget.state.page})
])
]),
domsugar('div', {class: 'camera-input'}, [video]),
domsugar('div', {class: 'edit-picture'}, button_list)
]);
gadget.element.replaceChild(div, gadget.element.firstElementChild);
});
} }
function gotStream(gadget, mediaStream) { // Capture the media stream
function captureAndRenderPicture(gadget) {
var image_capture = new window.ImageCapture(
gadget.element.querySelector('video').srcObject.getVideoTracks()[0]
),
div;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
var image_capture;
image_capture = new window.ImageCapture(mediaStream.getVideoTracks()[0]);
gadget.props.image_capture = image_capture;
return image_capture.getPhotoCapabilities(); return image_capture.getPhotoCapabilities();
}) })
.push(function (photoCapabilities) { .push(function (capabilities) {
gadget.props.image_width = photoCapabilities.imageWidth.max; return image_capture.takePhoto({imageWidth: capabilities.imageWidth.max});
gadget.props.image_height = photoCapabilities.imageHeight.max;
return gadget.props.video.play();
}) })
.push(function () { .push(function (blob) {
return setPageOne(gadget); gadget.detached_promise_dict.media_stream.cancel('Not needed anymore, as captured');
return RSVP.all([
gadget.getTranslationList(["Reset", "Confirm"]),
createImageBitmap(blob)
]);
})
.push(function (result_list) {
var // blob_url = URL.createObjectURL(blob),
// img = domsugar('img', {src: blob_url});
bitmap = result_list[1],
canvas = domsugar('canvas', {class: 'canvas'}),
defer = RSVP.defer();
// Prepare the cropper canvas
canvas.width = bitmap.width;
canvas.height = bitmap.height;
canvas.getContext('2d').drawImage(bitmap, 0, 0);
div = domsugar('div', {class: 'camera'}, [
domsugar('div', {class: 'camera-header'}, [
domsugar('h4', [
'Page ',
domsugar('label', {class: 'page-number', text: gadget.state.page})
])
]),
canvas,
domsugar('div', {class: 'edit-picture'}, [
domsugar('button', {type: 'button',
class: 'reset-btn ui-btn-icon-left ui-icon-times',
text: result_list[0][0]
}),
domsugar('button', {type: 'button',
class: 'confirm-btn ui-btn-icon-left ui-icon-check',
text: result_list[0][1]
})
])
]);
// XXX How to change the dom only when cropper is ready?
// For now, it needs to access dom element size
gadget.element.replaceChild(div, gadget.element.firstElementChild);
addDetachedPromise(gadget, 'cropper',
handleCropper(canvas,
gadget.state.preferred_cropped_canvas_data,
defer.resolve));
return defer.promise;
})
.push(function (cropper) {
gadget.cropper = cropper;
}); });
} }
function startStream(gadget) { function renderSubmittedPicture(gadget) {
return handleUserMedia(gadget, gotStream); var div = domsugar('div', {class: 'camera'}, [
domsugar('div', {class: 'camera-header'}, [
domsugar('h4', [
'Page ',
domsugar('label', {class: 'page-number', text: gadget.state.page})
])
]),
domsugar('img', {src: gadget.state.blob_url})
]);
// XXX How to change the dom only when cropper is ready?
// For now, it needs to access dom element size
gadget.element.replaceChild(div, gadget.element.firstElementChild);
} }
//////////////////////////////////////////////////
// Gadget API
//////////////////////////////////////////////////
rJS(window) rJS(window)
.declareAcquiredMethod(
"submitDialogWithCustomDialogMethod",
"submitDialogWithCustomDialogMethod"
)
.declareAcquiredMethod("getTranslationList", "getTranslationList")
.declareAcquiredMethod("notifySubmitted", "notifySubmitted")
.declareJob("startStream", function () {
return startStream(this);
})
.ready(function () { .ready(function () {
this.props = { this.detached_promise_dict = {};
video: this.element.querySelector(".video")
};
}) })
.declareMethod('render', function (options) { .declareJob('raise', function (error) {
var root = this.element, throw error;
camera_list = [], })
gadget = this; .declareService(function handleDetachedPromiseDict() {
// This service is responsable to cancel all ongoing detached promises
return this.getTranslationList(["Webcam is not available", "Reset", "Take Picture", "Confirm", "Edit", "Change Camera"]) // if the gadget is removed from the page
.push(function (result_list) { var gadget = this;
var i, return new RSVP.Promise(function () {return; }, function canceller(msg) {
button_list = root.querySelectorAll("button"); var key;
for (i = 0; i < button_list.length; i += 1) { for (key in gadget.detached_promise_dict) {
button_list[i].innerText = " " + result_list[i + 1]; if (gadget.detached_promise_dict.hasOwnProperty(key)) {
} gadget.detached_promise_dict[key].cancel(msg);
root.querySelector("video").innerText = result_list[0];
})
.push(function () {
var preferred_cropped_canvas_data = gadget.props.preferred_cropped_canvas_data;
preferred_cropped_canvas_data = preferred_cropped_canvas_data || JSON.parse(options.preferred_cropped_canvas_data);
gadget.props.dialog_method = preferred_cropped_canvas_data.dialog_method;
// Clear photo input
root.querySelector('.photoInput').value = "";
gadget.props.page_number = parseInt(root.querySelector('input[name="page-number"]').value, 10);
root.querySelector(".camera-input").style.display = "";
root.querySelector(".camera-output").style.display = "none";
if (!navigator.mediaDevices) {
throw ("mediaDevices is not supported");
}
gadget.props.preferred_cropped_canvas_data = preferred_cropped_canvas_data;
return navigator.mediaDevices.enumerateDevices();
})
.push(function (info_list) {
var j,
device,
len = info_list.length;
if (camera_list.length === 0) {
for (j = 0; j < len; j += 1) {
device = info_list[j];
if (device.kind === 'videoinput') {
camera_list.push(device);
}
}
}
if (camera_list.length >= 1) {
// trick to select back camera in mobile
gadget.props.device_id = camera_list[camera_list.length - 1].deviceId;
} }
gadget.props.camera_list = camera_list; }
return gadget.startStream(); });
})
.setState({
display_step: 'display_video',
page: 1
})
.declareMethod('render', function (options) {
// This method is called during the ERP5 form rendering
// changeState is used to ensure not resetting the gadget current display
// if not needed
var gadget = this;
return selectMediaDevice(gadget.state.device_id, false)
.push(function (device_id) {
return gadget.changeState({
dialog_method: options.dialog_method,
preferred_cropped_canvas_data: JSON.parse(options.preferred_cropped_canvas_data),
device_id: device_id,
key: options.key
});
}); });
}) })
.onStateChange(function () {
var gadget = this;
// ALL DOM modifications must be done only in this method
// this prevent concurrency issue on DOM access
if (gadget.state.display_step === 'display_video') {
return renderVideoCapture(gadget);
}
if (gadget.state.display_step === 'crop_picture') {
return captureAndRenderPicture(gadget);
}
if (gadget.state.display_step === 'submitting') {
return renderSubmittedPicture(gadget);
}
// Ease developper work by raising for not handled cases
throw new Error('Unhandled display step: ' + gadget.state.display_step);
})
.declareMethod('getContent', function () { .declareMethod('getContent', function () {
var input = this.element.querySelector('.photoInput'), var gadget = this,
result = {}; result = {};
if (gadget.state.display_step === 'submitting') {
result.field_your_document_scanner_gadget = JSON.stringify({ // do not send any content when sending the final form
"input_value": input.value, result[gadget.state.key] = JSON.stringify({
"preferred_cropped_canvas_data": this.props.preferred_cropped_canvas_data input_value: gadget.state.blob_url.split(';')[1].split(',')[1],
}); preferred_cropped_canvas_data: gadget.state.preferred_cropped_canvas_data
});
}
return result; return result;
}) })
.onEvent("click", function (evt) {
var e,
new_preferred_cropped_canvas_data,
gadget = this,
camera_list = this.props.camera_list,
root = this.element;
/*if (evt.target.name === "grayscale") {
return grayscale(root.querySelector(".canvas"),
root.querySelector('.photo'));
}*/
if (evt.target.className.indexOf("change-camera-btn") !== -1) {
evt.preventDefault();
for (e in camera_list) { .onEvent("click", function (evt) {
if (camera_list.hasOwnProperty(e)) { // Only handle click on BUTTON element
if (camera_list[e].deviceId !== gadget.props.device_id) { if (evt.target.tagName !== 'BUTTON') {
gadget.props.device_id = camera_list[e].deviceId; return;
break;
}
}
}
return gadget.startStream();
} }
var gadget = this;
// Disable any button. It must be managed by this gadget
evt.preventDefault();
gadget.element.querySelectorAll('button').forEach(function (elt) {
elt.disabled = true;
});
if (evt.target.className.indexOf("take-picture-btn") !== -1) { if (evt.target.className.indexOf("take-picture-btn") !== -1) {
evt.preventDefault(); return gadget.changeState({
return new RSVP.Queue() display_step: 'crop_picture'
.push(function () { });
disableButton(root);
root.querySelector(".camera").style.maxWidth = gadget.props.video.offsetWidth + "px";
return takePicture(gadget);
})
.push(function () {
root.querySelector(".camera-input").style.display = "none";
setPageTwo(root);
return enableButton(root);
});
} }
if (evt.target.className.indexOf("reset-btn") !== -1) { if (evt.target.className.indexOf("reset-btn") !== -1) {
evt.preventDefault(); return gadget.changeState({
root.querySelector(".camera-input").style.display = ""; display_step: 'display_video'
root.querySelector(".camera-output").style.display = "none"; });
root.querySelector('.photoInput').value = "";
gadget.props.cropper.destroy();
return setPageOne(gadget);
} }
if (evt.target.className.indexOf("confirm-btn") !== -1) { if (evt.target.className.indexOf("confirm-btn") !== -1) {
evt.preventDefault();
new_preferred_cropped_canvas_data = gadget.props.cropper.getData();
for (e in new_preferred_cropped_canvas_data) {
if (new_preferred_cropped_canvas_data.hasOwnProperty(e)) {
gadget.props.preferred_cropped_canvas_data[e] = new_preferred_cropped_canvas_data[e];
}
}
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
var canvas = gadget.props.cropper.getCroppedCanvas(); var canvas = gadget.cropper.getCroppedCanvas();
disableButton(gadget.element);
return new Promise(function (resolve) { return new Promise(function (resolve) {
canvas.toBlob(resolve, 'image/jpeg', 0.85); canvas.toBlob(resolve, 'image/jpeg', 0.85);
}); });
...@@ -346,22 +463,45 @@ ...@@ -346,22 +463,45 @@
.push(function (blob) { .push(function (blob) {
return jIO.util.readBlobAsDataURL(blob); return jIO.util.readBlobAsDataURL(blob);
}) })
.push(function (result) { .push(function (evt) {
var base64data = result.target.result, return gadget.changeState({
block = base64data.split(";"), blob_url: evt.target.result,
realData = block[1].split(",")[1]; preferred_cropped_canvas_data: gadget.cropper.getData(),
root.querySelector(".photo").src = base64data; display_step: 'submitting'
root.querySelector(".photoInput").value = realData; });
gadget.props.cropper.destroy();
}) })
.push(function () { .push(function () {
return gadget.submitDialogWithCustomDialogMethod(gadget.props.dialog_method); gadget.detached_promise_dict.cropper.cancel('Not needed anymore, as cropped');
return gadget.submitDialogWithCustomDialogMethod(gadget.state.dialog_method);
}) })
.push(function () { .push(function (evt) {
gadget.props.page_number = gadget.props.page_number + 1; return gadget.changeState({
root.querySelector('input[name="page-number"]').value = gadget.props.page_number; blob_url: undefined,
display_step: 'display_video',
page: gadget.state.page + 1
});
});
}
if (evt.target.className.indexOf("change-camera-btn") !== -1) {
return selectMediaDevice(gadget.state.device_id, true)
.push(function (device_id) {
return gadget.changeState({
display_step: 'display_video',
device_id: device_id
});
}); });
} }
}, false, false);
}(rJS, RSVP, window, navigator, Cropper, Promise, JSON, jIO)); throw new Error('Unhandled button: ' + evt.target.textContent);
\ No newline at end of file }, false, false)
.declareAcquiredMethod(
"submitDialogWithCustomDialogMethod",
"submitDialogWithCustomDialogMethod"
)
.declareAcquiredMethod("getTranslationList", "getTranslationList")
.declareAcquiredMethod("notifySubmitted", "notifySubmitted");
}(rJS, RSVP, window, document, navigator, Cropper, Promise, JSON, jIO, promiseEventListener, domsugar, createImageBitmap));
\ No newline at end of file
...@@ -7,6 +7,5 @@ selection_mapping = portal.portal_selections.getSelectionParamsFor( ...@@ -7,6 +7,5 @@ selection_mapping = portal.portal_selections.getSelectionParamsFor(
REQUEST=context.REQUEST) or {} REQUEST=context.REQUEST) or {}
canvas_data = selection_mapping.get(context.REQUEST["HTTP_USER_AGENT"]) or {} canvas_data = selection_mapping.get(context.REQUEST["HTTP_USER_AGENT"]) or {}
canvas_data["dialog_method"] = context.Base_storeDocumentFromCameraInActiveProcess.getId()
return json.dumps(canvas_data) return json.dumps(canvas_data)
portal = context.getPortalObject() portal = context.getPortalObject()
translateString = portal.Base_translateString translateString = portal.Base_translateString
active_process = context.Base_storeDocumentFromCameraInActiveProcess(
active_process_url=active_process_url,
batch_mode=True,
**kw)
# Avoid to pass huge images to the activity # Avoid to pass huge images to the activity
kw.pop("your_document_scanner_gadget", None) kw.pop("your_document_scanner_gadget", None)
context.activate().Base_uploadDocumentFromCamera( context.activate().Base_uploadDocumentFromCamera(
active_process_url=active_process.getRelativeUrl(), active_process_url=active_process_url,
**kw) **kw)
return context.Base_redirect('view', return context.Base_redirect('view',
......
...@@ -141,7 +141,7 @@ ...@@ -141,7 +141,7 @@
<dictionary> <dictionary>
<item> <item>
<key> <string>_text</string> </key> <key> <string>_text</string> </key>
<value> <string>python: [(\'preferred_cropped_canvas_data\', context.Base_getPreferredCropperSettingsFromSelection()),]</string> </value> <value> <string>python: [(\'dialog_method\', \'Base_storeDocumentFromCameraInActiveProcess\'), (\'preferred_cropped_canvas_data\', context.Base_getPreferredCropperSettingsFromSelection()),]</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
web_page_module/rjs_gadget_document_scanner_css
web_page_module/rjs_gadget_document_scanner_html
web_page_module/rjs_gadget_document_scanner_js
\ No newline at end of file
...@@ -101,20 +101,15 @@ ...@@ -101,20 +101,15 @@
<td>//button[@class="take-picture-btn ui-btn-icon-left ui-icon-circle"]</td> <td>//button[@class="take-picture-btn ui-btn-icon-left ui-icon-circle"]</td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>waitForCondition</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector("video").readyState == 4</td>
<td>30000</td>
</tr>
<tr> <tr>
<td>click</td> <td>click</td>
<td>//button[@class="take-picture-btn ui-btn-icon-left ui-icon-circle"]</td> <td>//button[@class="take-picture-btn ui-btn-icon-left ui-icon-circle"]</td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>waitForCondition</td> <td>waitForElementPresent</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector(".confirm-btn").style.display != "none"</td> <td>//button[@class="reset-btn ui-btn-icon-left ui-icon-times"]</td>
<td>30000</td> <td></td>
</tr> </tr>
<tr> <tr>
<td>click</td> <td>click</td>
...@@ -122,9 +117,9 @@ ...@@ -122,9 +117,9 @@
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>waitForCondition</td> <td>waitForElementPresent</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector(".confirm-btn").style.display == "none"</td> <td>//button[@class="take-picture-btn ui-btn-icon-left ui-icon-circle"]</td>
<td>30000</td> <td></td>
</tr> </tr>
<tr> <tr>
<td>click</td> <td>click</td>
...@@ -132,9 +127,9 @@ ...@@ -132,9 +127,9 @@
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>waitForCondition</td> <td>waitForElementPresent</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector(".confirm-btn").style.display != "none"</td> <td>//button[@class="confirm-btn ui-btn-icon-left ui-icon-check"]</td>
<td>3000</td> <td></td>
</tr> </tr>
<tr> <tr>
<td>click</td> <td>click</td>
...@@ -149,11 +144,6 @@ ...@@ -149,11 +144,6 @@
</div> </div>
<!--tr>
<td>waitForCondition</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector(".page-number").innerText == "2"</td>
<td>30000</td>
</tr-->
<tr> <tr>
<td>storeValue</td> <td>storeValue</td>
<td>//input[@id="field_your_active_process_url"]</td> <td>//input[@id="field_your_active_process_url"]</td>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment