1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +01:00

Bring in JX.Workflow and the inline commenting behavior, plus sync Javelin.

This commit is contained in:
epriestley 2011-02-01 15:52:04 -08:00
parent 4faad5b3f1
commit 9dac0ed9f1
26 changed files with 934 additions and 246 deletions

View file

@ -1,3 +1,19 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Placeholder so I don't forget about this, hopefully.

View file

@ -9,7 +9,7 @@
celerity_register_resource_map(array(
'aphront-dialog-view-css' =>
array(
'uri' => '/res/771b987d/rsrc/css/aphront/dialog-view.css',
'uri' => '/res/d98e6292/rsrc/css/aphront/dialog-view.css',
'type' => 'css',
'requires' =>
array(
@ -188,6 +188,15 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/css/core/core.css',
),
'phabricator-core-dialog-css' =>
array(
'uri' => '/res/d9580553/rsrc/css/core/dialog.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/core/dialog.css',
),
'phabricator-remarkup-css' =>
array(
'uri' => '/res/786989c3/rsrc/css/core/remarkup.css',
@ -226,6 +235,16 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js',
),
'javelin-behavior-differential-edit-inline-comments' =>
array(
'uri' => '/res/51d7da98/rsrc/js/application/differential/behavior-edit-inline-comments.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-lib-dev',
),
'disk' => '/rsrc/js/application/differential/behavior-edit-inline-comments.js',
),
'javelin-behavior-differential-populate' =>
array(
'uri' => '/res/f7efbf62/rsrc/js/application/differential/behavior-populate.js',
@ -246,9 +265,9 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/differential/behavior-show-more.js',
),
'javelin-init-dev' =>
'javelin-magical-init' =>
array(
'uri' => '/res/c57a9e89/rsrc/js/javelin/init.dev.js',
'uri' => '/res/76614f84/rsrc/js/javelin/init.dev.js',
'type' => 'js',
'requires' =>
array(
@ -257,7 +276,7 @@ celerity_register_resource_map(array(
),
'javelin-init-prod' =>
array(
'uri' => '/res/f0172c54/rsrc/js/javelin/init.min.js',
'uri' => '/res/ce6bff38/rsrc/js/javelin/init.min.js',
'type' => 'js',
'requires' =>
array(
@ -266,7 +285,7 @@ celerity_register_resource_map(array(
),
'javelin-lib-dev' =>
array(
'uri' => '/res/3e747182/rsrc/js/javelin/javelin.dev.js',
'uri' => '/res/29c9b6b4/rsrc/js/javelin/javelin.dev.js',
'type' => 'js',
'requires' =>
array(
@ -275,7 +294,7 @@ celerity_register_resource_map(array(
),
'javelin-lib-prod' =>
array(
'uri' => '/res/9438670e/rsrc/js/javelin/javelin.min.js',
'uri' => '/res/ef13c830/rsrc/js/javelin/javelin.min.js',
'type' => 'js',
'requires' =>
array(
@ -284,7 +303,7 @@ celerity_register_resource_map(array(
),
'javelin-typeahead-dev' =>
array(
'uri' => '/res/c81c0f01/rsrc/js/javelin/typeahead.dev.js',
'uri' => '/res/6de6ae59/rsrc/js/javelin/typeahead.dev.js',
'type' => 'js',
'requires' =>
array(
@ -293,17 +312,35 @@ celerity_register_resource_map(array(
),
'javelin-typeahead-prod' =>
array(
'uri' => '/res/1da2d984/rsrc/js/javelin/typeahead.min.js',
'uri' => '/res/593d9bb8/rsrc/js/javelin/typeahead.min.js',
'type' => 'js',
'requires' =>
array(
),
'disk' => '/rsrc/js/javelin/typeahead.min.js',
),
'javelin-workflow-dev' =>
array(
'uri' => '/res/2d740661/rsrc/js/javelin/workflow.dev.js',
'type' => 'js',
'requires' =>
array(
),
'disk' => '/rsrc/js/javelin/workflow.dev.js',
),
'javelin-workflow-prod' =>
array(
'uri' => '/res/b758e0a0/rsrc/js/javelin/workflow.min.js',
'type' => 'js',
'requires' =>
array(
),
'disk' => '/rsrc/js/javelin/workflow.min.js',
),
), array (
'packages' =>
array (
'd348c79d' =>
'dc2390af' =>
array (
'name' => 'core.pkg.css',
'symbols' =>
@ -320,7 +357,7 @@ celerity_register_resource_map(array(
9 => 'aphront-typeahead-control-css',
10 => 'phabricator-directory-css',
),
'uri' => '/res/pkg/d348c79d/core.pkg.css',
'uri' => '/res/pkg/dc2390af/core.pkg.css',
'type' => 'css',
),
'69b11588' =>
@ -340,17 +377,17 @@ celerity_register_resource_map(array(
),
'reverse' =>
array (
'phabricator-core-css' => 'd348c79d',
'phabricator-core-buttons-css' => 'd348c79d',
'phabricator-standard-page-view' => 'd348c79d',
'aphront-dialog-view-css' => 'd348c79d',
'aphront-form-view-css' => 'd348c79d',
'aphront-panel-view-css' => 'd348c79d',
'aphront-side-nav-view-css' => 'd348c79d',
'aphront-table-view-css' => 'd348c79d',
'aphront-tokenizer-control-css' => 'd348c79d',
'aphront-typeahead-control-css' => 'd348c79d',
'phabricator-directory-css' => 'd348c79d',
'phabricator-core-css' => 'dc2390af',
'phabricator-core-buttons-css' => 'dc2390af',
'phabricator-standard-page-view' => 'dc2390af',
'aphront-dialog-view-css' => 'dc2390af',
'aphront-form-view-css' => 'dc2390af',
'aphront-panel-view-css' => 'dc2390af',
'aphront-side-nav-view-css' => 'dc2390af',
'aphront-table-view-css' => 'dc2390af',
'aphront-tokenizer-control-css' => 'dc2390af',
'aphront-typeahead-control-css' => 'dc2390af',
'phabricator-directory-css' => 'dc2390af',
'differential-core-view-css' => '69b11588',
'differential-changeset-view-css' => '69b11588',
'differential-revision-detail-css' => '69b11588',

View file

@ -90,6 +90,7 @@ phutil_register_library_map(array(
'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents',
'DifferentialDiffViewController' => 'applications/differential/controller/diffview',
'DifferentialHunk' => 'applications/differential/storage/hunk',
'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment',
'DifferentialLintStatus' => 'applications/differential/constants/lintstatus',
'DifferentialMail' => 'applications/differential/mail/base',
'DifferentialMarkupEngineFactory' => 'applications/differential/parser/markup',
@ -264,6 +265,7 @@ phutil_register_library_map(array(
'DifferentialDiffTableOfContentsView' => 'AphrontView',
'DifferentialDiffViewController' => 'DifferentialController',
'DifferentialHunk' => 'DifferentialDAO',
'DifferentialInlineComment' => 'DifferentialDAO',
'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail',
'DifferentialReviewRequestMail' => 'DifferentialMail',
'DifferentialRevision' => 'DifferentialDAO',

View file

@ -79,6 +79,8 @@ class DifferentialRevisionViewController extends DifferentialController {
$changeset_view = new DifferentialChangesetListView();
$changeset_view->setChangesets($changesets);
$changeset_view->setEditable(true);
$changeset_view->setRevision($revision);
$comment_form = new DifferentialAddCommentView();
$comment_form->setRevision($revision);

View file

@ -0,0 +1,33 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class DifferentialInlineComment extends DifferentialDAO {
protected $revisionID;
protected $commentID;
protected $authorPHID;
protected $changesetID;
protected $isNewFile;
protected $lineNumber;
protected $lineLength;
protected $content;
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/differential/storage/base');
phutil_require_source('DifferentialInlineComment.php');

View file

@ -19,12 +19,24 @@
class DifferentialChangesetListView extends AphrontView {
private $changesets = array();
private $editable;
private $revision;
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setEditable($editable) {
$this->editable = $editable;
return $this;
}
public function setRevision(DifferentialRevision $revision) {
$this->revision = $revision;
return $this;
}
public function render() {
require_celerity_resource('differential-changeset-view-css');
@ -105,20 +117,14 @@ class DifferentialChangesetListView extends AphrontView {
Javelin::initBehavior('differential-show-more', array(
'uri' => '/differential/changeset/',
));
/*
Javelin::initBehavior('differential-context', array(
'uri' => $render_uri,
));
if ($edit) {
require_static('remarkup-css');
Javelin::initBehavior('differential-inline', array(
'uri' => '/differential/feedback/'.$revision->getID().'/',
if ($this->editable) {
$revision = $this->revision;
Javelin::initBehavior('differential-edit-inline-comments', array(
'uri' => '/differential/inline/edit/'.$revision->getID().'/',
));
}
*/
return
'<div class="differential-review-stage">'.
implode("\n", $output).

View file

@ -6,7 +6,10 @@
phutil_require_module('phabricator', 'applications/metamta/adapter/phpmailerlite');
phutil_require_module('phabricator', 'applications/metamta/storage/base');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phutil', 'utils');

View file

@ -24,31 +24,24 @@ function javelin_render_tag(
if (isset($attributes['sigil']) ||
isset($attributes['meta']) ||
isset($attributes['mustcapture'])) {
$classes = array();
foreach ($attributes as $k => $v) {
switch ($k) {
case 'sigil':
$classes[] = 'FN_'.$v;
$attributes['data-sigil'] = $v;
unset($attributes[$k]);
break;
case 'meta':
$response = CelerityAPI::getStaticResourceResponse();
$id = $response->addMetadata($v);
$classes[] = 'FD_'.$id;
$attributes['data-meta'] = $id;
unset($attributes[$k]);
break;
case 'mustcapture':
$classes[] = 'FI_CAPTURE';
$attributes['data-mustcapture'] = '1';
unset($attributes[$k]);
break;
}
}
if (isset($attributes['class'])) {
$classes[] = $attributes['class'];
}
$attributes['class'] = implode(' ', $classes);
}
return phutil_render_tag($tag, $attributes, $content);

View file

@ -68,12 +68,13 @@ class AphrontDialogView extends AphrontView {
'Cancel');
}
return phutil_render_tag(
return javelin_render_tag(
'form',
array(
'class' => 'aphront-dialog-view',
'action' => $this->submitURI,
'method' => 'post',
'sigil' => 'jx-dialog',
),
'<input type="hidden" name="__form__" value="1" />'.
'<div class="aphront-dialog-head">'.

View file

@ -7,6 +7,7 @@
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/base');
phutil_require_module('phutil', 'markup');

View file

@ -70,6 +70,7 @@ class PhabricatorStandardPageView extends AphrontPageView {
require_celerity_resource('phabricator-standard-page-view');
require_celerity_resource('javelin-lib-dev');
require_celerity_resource('javelin-workflow-dev');
$this->bodyContent = $this->renderChildren();
}

View file

@ -2,8 +2,6 @@
* @provides aphront-dialog-view-css
*/
.aphront-dialog-view {
width: 480px;
padding: 8px;
@ -40,3 +38,23 @@
margin-left: .5em;
}
.jx-client-dialog {
position: absolute;
z-index: 6;
}
.jx-mask {
opacity: .5;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=75)";
filter: alpha(opacity=75);
background: #999;
position: absolute;
z-index: 5;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
min-height: 100%;
}

View file

@ -119,3 +119,14 @@
margin: 0.5em 0;
padding: 10px 0px 20px;
}
.differential-reticle {
background: #ffeeaa;
border: 1px solid #ffcc00;
position: absolute;
z-index: 2;
opacity: 0.5;
top: 0px;
left: 0px;
}

View file

@ -0,0 +1,55 @@
/**
* @provides phabricator-core-dialog-css
*/
.jx-dialog {
display: block;
width: 480px;
padding: 8px;
background: #666;
margin: auto;
}
.jx-client-dialog {
position: absolute;
z-index: 6;
}
.jx-dialog .dialog-title {
background: #6d84b4;
border: none;
font-size: 15px;
font-weight: bold;
padding: 5px 12px 6px;
color: #ffffff;
}
.jx-dialog .dialog-body {
background: #ffffff;
padding: 16px 12px;
border: none;
overflow: hidden;
}
.jx-dialog .dialog-foot {
border: none;
background: #ededed;
padding: .5em;
text-align: right;
}
.jx-dialog button {
margin-left: 6px;
}
.jx-dialog input {
padding: 4px;
}
.jx-dialog .fields {
margin-top: 10px;
}
.jx-dialog input.block {
display: block;
margin: 3px 0 0 0;
}

View file

@ -0,0 +1,229 @@
/**
* @provides javelin-behavior-differential-edit-inline-comments
* @requires javelin-lib-dev
*/
JX.behavior('differential-edit-inline-comments', function(config) {
var selecting = false;
var reticle = JX.$N('div', {className: 'differential-reticle'});
JX.DOM.hide(reticle);
document.body.appendChild(reticle);
var origin = null;
var target = null;
var root = null;
var changeset = null;
var workflow = false;
var is_new = false;
function updateReticle() {
var top = origin;
var bot = target;
if (JX.$V(top).y > JX.$V(bot).y) {
var tmp = top;
top = bot;
bot = tmp;
}
var code = target.nextSibling;
var pos = JX.$V(top).add(1 + JX.$V.getDim(target).x, 0);
var dim = JX.$V.getDim(code).add(-4, 0);
dim.y = (JX.$V(bot).y - pos.y) + JX.$V.getDim(bot).y;
pos.setPos(reticle);
dim.setDim(reticle);
JX.DOM.show(reticle);
}
function hideReticle() {
JX.DOM.hide(reticle);
}
function finishSelect() {
selecting = false;
workflow = false;
hideReticle();
}
function drawInlineComment(table, anchor, r) {
copyRows(table, JX.$N('div', JX.HTML(r.markup)), anchor);
finishSelect();
}
function isNewFile(node) {
return node.parentNode.firstChild != node;
}
function getRowNumber(th_node) {
try {
return parseInt(th_node.id.match(/^C\d+[ON]L(\d+)$/)[1], 10);
} catch (x) {
return undefined;
}
}
JX.Stratcom.listen(
'mousedown',
['differential-changeset', 'tag:th'],
function(e) {
if (workflow ||
selecting ||
getRowNumber(e.getTarget()) === undefined) {
return;
}
selecting = true;
root = e.getNode('differential-changeset');
origin = target = e.getTarget();
var data = e.getNodeData('differential-changeset');
if (isNewFile(target)) {
changeset = data.oid;
} else {
changeset = data.nid;
}
updateReticle();
e.kill();
});
JX.Stratcom.listen(
'mouseover',
['differential-changeset', 'tag:th'],
function(e) {
if (!selecting ||
workflow ||
(getRowNumber(e.getTarget()) === undefined) ||
(isNewFile(e.getTarget()) != isNewFile(origin)) ||
(e.getNode('differential-changeset') !== root)) {
return;
}
target = e.getTarget();
updateReticle();
});
JX.Stratcom.listen(
'mouseup',
null,
function(e) {
if (workflow || !selecting) {
return;
}
var o = getRowNumber(origin);
var t = getRowNumber(target);
var insert;
var len;
if (t < o) {
len = (o - t);
o = t;
insert = origin.parentNode;
} else {
len = (t - o);
insert = target.parentNode;
}
var data = {
op: 'new',
changeset: changeset,
number: o,
length: len,
is_new: isNewFile(target) ? 1 : 0
};
workflow = true;
var w = new JX.Workflow(config.uri, data)
.setHandler(function(r) {
// Skip over any rows which contain inline feedback. Don't mimic this!
// We're shipping around raw HTML here for performance reasons, but
// normally you should use sigils to encode this kind of data on
// the document.
var target = insert.nextSibling;
while (target &&
(!JX.DOM.isType(target, 'tr')
|| target.className.indexOf('inline') !== -1)) {
target = target.nextSibling;
}
drawInlineComment(insert.parentNode, target, r);
finishSelect();
JX.Stratcom.invoke('inline-comment-update',
null,
{id : r.inlineCommentID});
})
.setCloseHandler(finishSelect);
w.listen('error', function(e) {
// TODO: uh, tell the user I guess
finishSelect();
JX.Stratcom.context().stop();
});
w.start();
e.kill();
});
JX.Stratcom.listen(
['mouseover', 'mouseout'],
'inline-comment',
function(e) {
if (selecting || workflow) {
return;
}
if (e.getType() == 'mouseout') {
hideReticle();
} else {
var data = e.getNodeData('inline-comment');
var change = e.getNodeData('differential-changeset');
root = e.getNode('differential-changeset');
var prefix = 'C' + change;
if (data.is_new) {
prefix += 'NL';
} else {
prefix += 'OL';
}
origin = JX.$(prefix + data.number);
target = JX.$(prefix + (data.number + data.length));
updateReticle();
}
});
JX.Stratcom.listen(
'click',
[['inline-comment', 'delete'],
['inline-comment', 'edit']],
function(e) {
var data = {
op: e.getNode('edit') ? 'edit' : 'delete',
id: e.getNodeData('inline-comment').id
};
new JX.Workflow(config.uri, data)
.setHandler(function(r) {
var base_row = e.getNode('inline-comment').parentNode.parentNode;
if (data.op == 'edit' && r.markup) {
drawInlineComment(base_row.parentNode, base_row, r);
}
JX.DOM.remove(base_row);
JX.Stratcom.invoke('differential-inline-comment-update');
})
.start();
e.kill();
});
});

View file

@ -1,4 +1,3 @@
/** @provides javelin-init-dev */
/**
* Javelin core; installs Javelin and Stratcom event delegation.
*
@ -31,7 +30,6 @@
JX.__rawEventQueue = function(what) {
master_event_queue.push(what);
// Evade static analysis - JX.Stratcom
var Stratcom = JX['Stratcom'];
if (Stratcom && Stratcom.ready) {
@ -63,7 +61,8 @@
var target = what.srcElement || what.target;
if (target &&
(what.type in {click: 1, submit: 1}) &&
(/ FI_CAPTURE /).test(' ' + target.className + ' ')) {
target.getAttribute &&
target.getAttribute('data-mustcapture') === '1') {
what.returnValue = false;
what.preventDefault && what.preventDefault();
document.body.id = 'event_capture';

View file

@ -1,2 +1,2 @@
/** @provides javelin-init-prod */
(function(){if(window.JX)return;window.JX={};window.__DEV__=window.__DEV__||0;var d=false;var f=[];var e=[];var h=document.documentElement;var b=!!h.addEventListener;JX.__rawEventQueue=function(o){e.push(o);var j=JX.Stratcom;if(j&&j.ready){var m=e;e=[];for(var l=0;l<m.length;++l){var k=m[l];try{var test=k.type;}catch(p){continue;}if(!d&&k.type=='domready'){document.body&&(document.body.id=null);d=true;for(var l=0;l<f.length;l++)f[l]();}j.dispatch(k);}}else{var n=o.srcElement||o.target;if(n&&(o.type in {click:1,submit:1})&&(/ FI_CAPTURE /).test(' '+n.className+' ')){o.returnValue=false;o.preventDefault&&o.preventDefault();document.body.id='event_capture';if(!add_event_listener&&document.createEventObject){e.pop();e.push(document.createEventObject(o));}return false;}}};JX.enableDispatch=function(j,k){if(j.addEventListener){j.addEventListener(k,JX.__rawEventQueue,true);}else if(j.attachEvent)j.attachEvent('on'+k,JX.__rawEventQueue);};var a=['click','change','keypress','mousedown','mouseover','mouseout','mouseup','keydown','drop','dragenter','dragleave','dragover'];if(!b)a.push('focusin','focusout');if(window.opera){a.push('focus','blur');}else a.push('submit');for(var c=0;c<a.length;++c)JX.enableDispatch(h,a[c]);var i=[('onpagehide' in window)?'pagehide':'unload','resize','focus','blur'];for(var c=0;c<i.length;++c)JX.enableDispatch(window,i[c]);JX.__simulate=function(k,event){if(!b){var j={target:k,type:event};JX.__rawEventQueue(j);if(j.returnValue===false)return false;}};if(b){document.addEventListener('DOMContentLoaded',function(){JX.__rawEventQueue({type:'domready'});},true);}else{var g="if (this.readyState == 'complete') {"+"JX.__rawEventQueue({type: 'domready'});"+"}";document.write('<script'+' defer="defer"'+' src="javascript:void(0)"'+' onreadystatechange="'+g+'"'+'><\/sc'+'ript\>');}JX.onload=function(j){if(d){j();}else f.push(j);};})();
(function(){if(window.JX)return;window.JX={};window.__DEV__=window.__DEV__||0;var d=false;var f=[];var e=[];var h=document.documentElement;var b=!!h.addEventListener;JX.__rawEventQueue=function(o){e.push(o);var j=JX.Stratcom;if(j&&j.ready){var m=e;e=[];for(var l=0;l<m.length;++l){var k=m[l];try{var test=k.type;}catch(p){continue;}if(!d&&k.type=='domready'){document.body&&(document.body.id=null);d=true;for(var l=0;l<f.length;l++)f[l]();}j.dispatch(k);}}else{var n=o.srcElement||o.target;if(n&&(o.type in {click:1,submit:1})&&n.getAttribute&&n.getAttribute('data-mustcapture')==='1'){o.returnValue=false;o.preventDefault&&o.preventDefault();document.body.id='event_capture';if(!add_event_listener&&document.createEventObject){e.pop();e.push(document.createEventObject(o));}return false;}}};JX.enableDispatch=function(j,k){if(j.addEventListener){j.addEventListener(k,JX.__rawEventQueue,true);}else if(j.attachEvent)j.attachEvent('on'+k,JX.__rawEventQueue);};var a=['click','change','keypress','mousedown','mouseover','mouseout','mouseup','keydown','drop','dragenter','dragleave','dragover'];if(!b)a.push('focusin','focusout');if(window.opera){a.push('focus','blur');}else a.push('submit');for(var c=0;c<a.length;++c)JX.enableDispatch(h,a[c]);var i=[('onpagehide' in window)?'pagehide':'unload','resize','focus','blur'];for(var c=0;c<i.length;++c)JX.enableDispatch(window,i[c]);JX.__simulate=function(k,event){if(!b){var j={target:k,type:event};JX.__rawEventQueue(j);if(j.returnValue===false)return false;}};if(b){document.addEventListener('DOMContentLoaded',function(){JX.__rawEventQueue({type:'domready'});},true);}else{var g="if (this.readyState == 'complete') {"+"JX.__rawEventQueue({type: 'domready'});"+"}";document.write('<script'+' defer="defer"'+' src="javascript:void(0)"'+' onreadystatechange="'+g+'"'+'><\/sc'+'ript\>');}JX.onload=function(j){if(d){j();}else f.push(j);};})();

View file

@ -824,7 +824,6 @@ JX.install('Event', {
* }
* });
*
*
* @return string|null ##null## if there is no associated special key,
* or one of the strings 'delete', 'tab', 'return',
* 'esc', 'left', 'up', 'right', or 'down'.
@ -836,21 +835,17 @@ JX.install('Event', {
return null;
}
var c = r.keyCode;
do {
c = JX.Event._keymap[c] || null;
} while (c && JX.Event._keymap[c])
return c;
return JX.Event._keymap[r.keyCode] || null;
},
/**
* Get the node corresponding to the specified key in this event's node map.
* This is a simple helper method that makes the API for accessing nodes
* less ugly.
*
* JX.Stratcom.listen('click', 'tag:a', function(e) {
* var a = e.getNode('nearest:a');
* var a = e.getNode('tag:a');
* // do something with the link that was clicked
* });
*
@ -864,8 +859,27 @@ JX.install('Event', {
*/
getNode : function(key) {
return this.getNodes()[key] || null;
}
},
/**
* Get the metadata associated with the node that corresponds to the key
* in this event's node map. This is a simple helper method that makes
* the API for accessing metadata associated with specific nodes less ugly.
*
* JX.Stratcom.listen('click', 'tag:a', function(event) {
* var anchorData = event.getNodeData('tag:a');
* // do something with the metadata of the link that was clicked
* });
*
* @param string sigil or stratcom node key
* @return dict dictionary of the node's metadata
* @task info
*/
getNodeData : function(key) {
// Evade static analysis - JX.Stratcom
return JX['Stratcom'].getData(this.getNode(key));
}
},
statics : {
@ -878,10 +892,10 @@ JX.install('Event', {
38 : 'up',
39 : 'right',
40 : 'down',
63232 : 38,
63233 : 40,
62234 : 37,
62235 : 39
63232 : 'up',
63233 : 'down',
62234 : 'left',
62235 : 'right'
}
},
@ -1008,6 +1022,7 @@ JX.install('Event', {
* @task listen Listening to Events
* @task handle Responding to Events
* @task sigil Managing Sigils
* @task meta Managing Metadata
* @task internal Internals
*/
JX.install('Stratcom', {
@ -1016,8 +1031,6 @@ JX.install('Stratcom', {
_targets : {},
_handlers : [],
_need : {},
_matchName : /\bFN_([^ ]+)/,
_matchData : /\bFD_([^ ]+)_([^ ]+)/,
_auto : '*',
_data : {},
_execContext : [],
@ -1039,13 +1052,13 @@ JX.install('Stratcom', {
/**
* Within each datablock, data is identified by a unique index. The data
* pointer on a node looks like this:
* pointer (data-meta attribute) on a node looks like this:
*
* FD_1_2
* 1_2
*
* ...where 1 is the block, and 2 is the index within that block. Normally,
* blocks are filled on the server side, so index allocation takes place
* there. However, when data is provided with JX.Stratcom.sigilize(), we
* there. However, when data is provided with JX.Stratcom.addData(), we
* need to allocate indexes on the client.
*/
_dataIndex : 0,
@ -1185,9 +1198,12 @@ JX.install('Stratcom', {
if (path[kk] == 'tag:#document') {
throw new Error(
'JX.Stratcom.listen(..., "tag:#document", ...): ' +
'listen for document events as "tag:window", not ' +
'"tag:#document", in order to get consistent behavior ' +
'across browsers.');
'listen for all events using null, not "tag:#document"');
}
if (path[kk] == 'tag:window') {
throw new Error(
'JX.Stratcom.listen(..., "tag:window", ...): ' +
'listen for window events using null, not "tag:window"');
}
}
if (!type_target[path[kk]]) {
@ -1219,29 +1235,27 @@ JX.install('Stratcom', {
* @task internal
*/
dispatch : function(event) {
// TODO: simplify this :P
var target;
try {
target = event.srcElement || event.target;
if (target === window || (!target || target.nodeName == '#document')) {
target = {nodeName: 'window'};
}
} catch (x) {
target = null;
}
var path = [];
var nodes = {};
var push = function(key, node) {
// we explicitly only store the first occurrence of each key
if (!(key in nodes)) {
if (!nodes.hasOwnProperty(key)) {
nodes[key] = node;
path.push(key);
}
};
var target = event.srcElement || event.target;
// Since you can only listen by tag, id or sigil, which are all
// attributes of an Element (the DOM interface), we unset the target
// if it isn't an Element (window and Document are Nodes but not Elements)
if (!target || !target.getAttribute) {
target = null;
}
var cursor = target;
while (cursor) {
while (cursor && cursor.getAttribute) {
push('tag:' + cursor.nodeName.toLowerCase(), cursor);
var id = cursor.id;
@ -1249,11 +1263,12 @@ JX.install('Stratcom', {
push('id:' + id, cursor);
}
var source = cursor.className || '';
// className is an SVGAnimatedString for SVG elements, use baseVal
var token = ((source.baseVal || source).match(this._matchName) || [])[1];
if (token) {
push(token, cursor);
var sigils = cursor.getAttribute('data-sigil');
if (sigils) {
sigils = sigils.split(' ');
for (var ii = 0; ii < sigils.length; ii++) {
push(sigils[ii], cursor);
}
}
cursor = cursor.parentNode;
@ -1264,16 +1279,10 @@ JX.install('Stratcom', {
etype = this._typeMap[etype];
}
var data = {};
for (var key in nodes) {
data[key] = this.getData(nodes[key]);
}
var proxy = new JX.Event()
.setRawEvent(event)
.setType(etype)
.setTarget(target)
.setData(data)
.setNodes(nodes)
.setPath(path.reverse());
@ -1408,40 +1417,6 @@ JX.install('Stratcom', {
},
/**
* Attach a sigil (and, optionally, metadata) to a node. Note that you can
* not overwrite, remove or replace a sigil.
*
* @param Node Node without any sigil.
* @param string Sigil to name the node with.
* @param object? Optional metadata object to attach to the node.
* @return void
* @task sigil
*/
sigilize : function(node, sigil, data) {
if (__DEV__) {
if (node.className.match(this._matchName)) {
throw new Error(
'JX.Stratcom.sigilize(<node>, ' + sigil + ', ...): ' +
'node already has a sigil, sigils may not be overwritten.');
}
if (typeof data != 'undefined' &&
(data === null || typeof data != 'object')) {
throw new Error(
'JX.Stratcom.sigilize(..., ..., <nonobject>): ' +
'data to attach to node is not an object. You must use ' +
'objects, not primitives, for metadata.');
}
}
if (data) {
JX.Stratcom._setData(node, data);
}
node.className = 'FN_' + sigil + ' ' + node.className;
},
/**
* Determine if a node has a specific sigil.
*
@ -1452,14 +1427,44 @@ JX.install('Stratcom', {
* @task sigil
*/
hasSigil : function(node, sigil) {
if (!node.className) {
// Some nodes don't have a className, notably 'document'. We hit
// 'document' when following .parentNode chains, e.g. in
// JX.DOM.nearest(), so exit early if we don't have a className to avoid
// fataling on 'node.className.match' being undefined.
return false;
if (__DEV__) {
if (!node || !node.getAttribute) {
throw new Error(
'JX.Stratcom.hasSigil(<non-element>, ...): ' +
'node is not an element. Most likely, you\'re passing window or ' +
'document, which are not elements and can\'t have sigils.');
}
return (node.className.match(this._matchName) || [])[1] == sigil;
}
var sigils = node.getAttribute('data-sigil');
return sigils && (' ' + sigils + ' ').indexOf(' ' + sigil + ' ') > -1;
},
/**
* Add a sigil to a node.
*
* @param Node Node to add the sigil to.
* @param string Sigil to name the node with.
* @return void
* @task sigil
*/
addSigil: function(node, sigil) {
if (__DEV__) {
if (!node || !node.getAttribute) {
throw new Error(
'JX.Stratcom.addSigil(<non-element>, ...): ' +
'node is not an element. Most likely, you\'re passing window or ' +
'document, which are not elements and can\'t have sigils.');
}
}
var sigils = node.getAttribute('data-sigil');
if (sigils && !JX.Stratcom.hasSigil(node, sigil)) {
sigil = sigils + ' ' + sigil;
}
node.setAttribute('data-sigil', sigil);
},
@ -1467,60 +1472,75 @@ JX.install('Stratcom', {
* Retrieve a node's metadata.
*
* @param Node Node from which to retrieve data.
* @return object Data attached to the node, or an empty dictionary if
* the node has no data attached. In this case, the empty
* dictionary is set as the node's metadata -- i.e.,
* subsequent calls to getData() will retrieve the same
* object.
*
* @task sigil
* @return object Data attached to the node. If no data has been attached
* to the node yet, an empty object will be returned, but
* subsequent calls to this method will always retrieve the
* same object.
* @task meta
*/
getData : function(node) {
if (__DEV__) {
if (!node) {
if (!node || !node.getAttribute) {
throw new Error(
'JX.Stratcom.getData(<empty>): ' +
'you must provide a node to get associated data from.');
'JX.Stratcom.getData(<non-element>): ' +
'node is not an element. Most likely, you\'re passing window or ' +
'document, which are not elements and can\'t have data.');
}
}
var matches = (node.className || '').match(this._matchData);
if (matches) {
var block = this._data[matches[1]];
var index = matches[2];
var meta_id = (node.getAttribute('data-meta') || '').split('_');
if (meta_id[0] && meta_id[1]) {
var block = this._data[meta_id[0]];
var index = meta_id[1];
if (block && (index in block)) {
return block[index];
}
}
return JX.Stratcom._setData(node, {});
var data = {};
if (!this._data[1]) { // data block 1 is reserved for JavaScript
this._data[1] = {};
}
this._data[1][this._dataIndex] = data;
node.setAttribute('data-meta', '1_' + (this._dataIndex++));
return data;
},
/**
/**
* Add data to a node's metadata.
*
* @param Node Node which data should be attached to.
* @param object Data to add to the node's metadata.
* @return object Data attached to the node that is returned by
* JX.Stratcom.getData().
* @task meta
*/
addData : function(node, data) {
if (__DEV__) {
if (!node || !node.getAttribute) {
throw new Error(
'JX.Stratcom.addData(<non-element>, ...): ' +
'node is not an element. Most likely, you\'re passing window or ' +
'document, which are not elements and can\'t have sigils.');
}
if (!data || typeof data != 'object') {
throw new Error(
'JX.Stratcom.addData(..., <nonobject>): ' +
'data to attach to node is not an object. You must use ' +
'objects, not primitives, for metadata.');
}
}
return JX.copy(JX.Stratcom.getData(node), data);
},
/**
* @task internal
*/
allocateMetadataBlock : function() {
return this._dataBlock++;
},
/**
* Attach metadata to a node. This data can later be retrieved through
* @{JX.Stratcom.getData()}, or @{JX.Event.getData()}.
*
* @param Node Node which data should be attached to.
* @param object Data to attach.
* @return object Attached data.
*
* @task internal
*/
_setData : function(node, data) {
if (!this._data[1]) { // data block 1 is reserved for javascript
this._data[1] = {};
}
this._data[1][this._dataIndex] = data;
node.className = 'FD_1_' + (this._dataIndex++) + ' ' + node.className;
return data;
}
}
});
@ -1535,7 +1555,7 @@ JX.install('Stratcom', {
JX.behavior = function(name, control_function) {
if (__DEV__) {
if (name in JX.behavior._behaviors) {
if (JX.behavior._behaviors.hasOwnProperty(name)) {
throw new Error(
'JX.behavior("'+name+'", ...): '+
'behavior is already registered.');
@ -1566,7 +1586,7 @@ JX.initBehaviors = function(map) {
}
var configs = map[name];
if (!configs.length) {
if (name in JX.behavior._initialized) {
if (JX.behavior._initialized.hasOwnProperty(name)) {
continue;
} else {
configs = [null];
@ -1827,7 +1847,7 @@ JX.install('Request', {
},
initialize : function() {
JX.Stratcom.listen('unload', 'tag:window', JX.Request.shutdown);
JX.Stratcom.listen('unload', null, JX.Request.shutdown);
}
});
@ -2410,8 +2430,12 @@ JX.$N = function(tag, attr, content) {
}
if (attr.sigil) {
JX.Stratcom.sigilize(node, attr.sigil, attr.meta);
JX.Stratcom.addSigil(node, attr.sigil);
delete attr.sigil;
}
if (attr.meta) {
JX.Stratcom.addData(node, attr.meta);
delete attr.meta;
}
@ -2421,17 +2445,6 @@ JX.$N = function(tag, attr, content) {
'$N(' + tag + ', ...): ' +
'use the key "meta" to specify metadata, not "data" or "metadata".');
}
if (attr.meta) {
throw new Error(
'$N(' + tag + ', ...): ' +
'if you specify "meta" metadata, you must also specify a "sigil".');
}
}
// prevent sigil from being wiped by blind copying the className
if (attr.className) {
JX.DOM.alterClass(node, attr.className, true);
delete attr.className;
}
JX.copy(node, attr);
@ -2595,7 +2608,7 @@ JX.install('DOM', {
* @author jgabbard
*/
nearest : function(node, sigil) {
while (node && !JX.Stratcom.hasSigil(node, sigil)) {
while (node && node.getAttribute && !JX.Stratcom.hasSigil(node, sigil)) {
node = node.parentNode;
}
return node;

File diff suppressed because one or more lines are too long

View file

@ -86,7 +86,7 @@ JX.install('Typeahead', {
'mousedown',
'tag:a',
JX.bind(this, function(e) {
this._choose(e.getTarget());
this._choose(e.getNode('tag:a'));
e.prevent();
}));
@ -601,8 +601,14 @@ JX.install('TypeaheadSource', {
var n = Math.min(this.getMaximumResultCount(), hits.length);
var nodes = [];
for (var kk = 0; kk < n; kk++) {
var data = this._raw[hits[kk]];
nodes.push(JX.$N(
nodes.push(this.createNode(this._raw[hits[kk]]));
}
this._typeahead.showResults(nodes);
},
createNode : function(data) {
return JX.$N(
'a',
{
href: data.uri,
@ -610,11 +616,10 @@ JX.install('TypeaheadSource', {
rel: data.id,
className: 'jx-result'
},
data.display));
}
this._typeahead.showResults(nodes);
data.display
);
},
normalize : function(str) {
return (this.getNormalizer() || JX.bag())(str);
},
@ -850,11 +855,7 @@ JX.install('Tokenizer', {
this._tokens = [];
this._tokenMap = {};
var focus = JX.$N('input', {
className: 'jx-tokenizer-input',
type: 'text',
value: this._orig.value
});
var focus = this.buildInput(this._orig.value);
this._focus = focus;
JX.DOM.listen(
@ -870,8 +871,8 @@ JX.install('Tokenizer', {
JX.bind(
this,
function(e) {
if (e.getNodes().remove) {
this._remove(e.getData().token.key);
if (e.getNode('remove')) {
this._remove(e.getNodeData('token').key);
} else if (e.getTarget() == this._root) {
this.focus();
}
@ -1011,20 +1012,7 @@ JX.install('Tokenizer', {
var focus = this._focus;
var root = this._root;
var token = JX.$N('a', {
className: 'jx-tokenizer-token'
}, value);
var input = JX.$N('input', {
type: 'hidden',
value: key,
name: this._orig.name+'['+(this._seq++)+']'
});
var remove = JX.$N('a', {
className: 'jx-tokenizer-x'
}, JX.HTML('&times;'));
var token = this.buildToken(key, value);
this._tokenMap[key] = {
value : value,
@ -1033,17 +1021,42 @@ JX.install('Tokenizer', {
};
this._tokens.push(key);
JX.Stratcom.sigilize(token, 'token', {key : key});
JX.Stratcom.sigilize(remove, 'remove');
token.appendChild(input);
token.appendChild(remove);
root.insertBefore(token, focus);
return true;
},
buildInput: function(value) {
return JX.$N('input', {
className: 'jx-tokenizer-input',
type: 'text',
value: value
});
},
/**
* Generate a token based on a key and value. The "token" and "remove"
* sigils are observed by a listener in start().
*/
buildToken: function(key, value) {
var input = JX.$N('input', {
type: 'hidden',
value: key,
name: this._orig.name + '[' + (this._seq++) + ']'
});
var remove = JX.$N('a', {
className: 'jx-tokenizer-x',
sigil: 'remove'
}, JX.HTML('&times;'));
return JX.$N('a', {
className: 'jx-tokenizer-token',
sigil: 'token',
meta: {key: key}
}, [value, input, remove]);
},
getTokens : function() {
var result = {};
for (var key in this._tokenMap) {

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,239 @@
/** @provides javelin-workflow-dev */
/**
* @requires javelin-install javelin-vector javelin-dom
* @provides javelin-mask
* @javelin
*/
/**
* Show a transparent "mask" over the page; used by Workflow to draw visual
* attention to modal dialogs.
*/
JX.install('Mask', {
statics : {
_depth : 0,
_mask : null,
show : function() {
if (!JX.Mask._depth) {
JX.Mask._mask = JX.$N('div', {className: 'jx-mask'});
document.body.appendChild(JX.Mask._mask);
JX.$V.getDocument().setDim(JX.Mask._mask);
}
++JX.Mask._depth;
},
hide : function() {
--JX.Mask._depth;
if (!JX.Mask._depth) {
JX.DOM.remove(JX.Mask._mask);
JX.Mask._mask = null;
}
}
}
});
/**
* @requires javelin-stratcom
* javelin-request
* javelin-dom
* javelin-vector
* javelin-install
* javelin-util
* javelin-mask
* @provides javelin-workflow
* @javelin
*/
JX.install('Workflow', {
construct : function(uri, data) {
if (__DEV__) {
if (!uri || uri == '#') {
throw new Error(
'new JX.Workflow(<?>, ...): '+
'bogus URI provided when creating workflow.');
}
}
this.setURI(uri);
this.setData(data || {});
},
events : ['error', 'finally', 'submit'],
statics : {
_stack : [],
newFromForm : function(form, data) {
var inputs = [].concat(
JX.DOM.scry(form, 'input'),
JX.DOM.scry(form, 'button'),
JX.DOM.scry(form, 'textarea'));
for (var ii = 0; ii < inputs.length; ii++) {
if (inputs[ii].disabled) {
delete inputs[ii];
} else {
inputs[ii].disabled = true;
}
}
var workflow = new JX.Workflow(
form.getAttribute('action'),
JX.copy(data || {}, JX.DOM.serialize(form)));
workflow.setMethod(form.getAttribute('method'));
workflow.listen('finally', function() {
for (var ii = 0; ii < inputs.length; ii++) {
inputs[ii] && (inputs[ii].disabled = false);
}
});
return workflow;
},
newFromLink : function(link) {
var workflow = new JX.Workflow(link.href);
return workflow;
},
_push : function(workflow) {
JX.Mask.show();
JX.Workflow._stack.push(workflow);
},
_pop : function() {
var dialog = JX.Workflow._stack.pop();
(dialog.getCloseHandler() || JX.bag)();
dialog._destroy();
JX.Mask.hide();
},
disable : function() {
JX.Workflow._disabled = true;
},
_onbutton : function(event) {
if (JX.Stratcom.pass()) {
return;
}
if (JX.Workflow._disabled) {
return;
}
var t = event.getTarget();
if (t.name == '__cancel__' || t.name == '__close__') {
JX.Workflow._pop();
} else {
var form = event.getNode('jx-dialog');
var data = JX.DOM.serialize(form);
data[t.name] = true;
data.__wflow__ = true;
var active = JX.Workflow._stack[JX.Workflow._stack.length - 1];
var e = active.invoke('submit', {form: form, data: data});
if (!e.getStopped()) {
active._destroy();
active
.setURI(form.getAttribute('action') || active.getURI())
.setData(data)
.start();
}
}
event.prevent();
}
},
members : {
_root : null,
_pushed : false,
_onload : function(r) {
// It is permissible to send back a falsey redirect to force a page
// reload, so we need to take this branch if the key is present.
if (r && (typeof r.redirect != 'undefined')) {
JX.go(r.redirect, true);
} else if (r && r.dialog) {
this._push();
this._root = JX.$N(
'div',
{className: 'jx-client-dialog'},
JX.HTML(r.dialog));
JX.DOM.listen(
this._root,
'click',
'tag:button',
JX.Workflow._onbutton);
document.body.appendChild(this._root);
var d = JX.$V.getDim(this._root);
var v = JX.$V.getViewport();
var s = JX.$V.getScroll();
JX.$V((v.x - d.x) / 2, s.y + 100).setPos(this._root);
try {
JX.DOM.focus(JX.DOM.find(this._root, 'button', '__default__'));
var inputs = JX.DOM.scry(this._root, 'input')
.concat(JX.DOM.scry(this._root, 'textarea'));
var miny = Number.POSITIVE_INFINITY;
var target = null;
for (var ii = 0; ii < inputs.length; ++ii) {
if (inputs[ii].type != 'hidden') {
// Find the topleft-most displayed element.
var p = JX.$V(inputs[ii]);
if (p.y < miny) {
miny = p.y;
target = inputs[ii];
}
}
}
target && JX.DOM.focus(target);
} catch (_ignored) {}
} else if (this.getHandler()) {
this.getHandler()(r);
this._pop();
} else if (r) {
if (__DEV__) {
throw new Error('Response to workflow request went unhandled.');
}
}
},
_push : function() {
if (!this._pushed) {
this._pushed = true;
JX.Workflow._push(this);
}
},
_pop : function() {
if (this._pushed) {
this._pushed = false;
JX.Workflow._pop();
}
},
_destroy : function() {
if (this._root) {
JX.DOM.remove(this._root);
this._root = null;
}
},
start : function() {
var uri = this.getURI();
var method = this.getMethod();
var r = new JX.Request(uri, JX.bind(this, this._onload));
r.setData(this.getData());
r.setDataSerializer(this.getDataSerializer());
if (method) {
r.setMethod(method);
}
r.listen('finally', JX.bind(this, this.invoke, 'finally'));
r.listen('error', JX.bind(this, function(error) {
var e = this.invoke('error', error);
if (e.getStopped()) {
return;
}
// TODO: Default error behavior? On Facebook Lite, we just shipped the
// user to "/error/". We could emit a blanket 'workflow-failed' type
// event instead.
}));
r.send();
}
},
properties : {
handler : null,
closeHandler : null,
data : null,
dataSerializer : null,
method : null,
URI : null
}
});

View file

@ -0,0 +1,3 @@
/** @provides javelin-workflow-prod */
JX.install('Mask',{statics:{_a:0,_b:null,show:function(){if(!JX.Mask._a){JX.Mask._b=JX.$N('div',{className:'jx-mask'});document.body.appendChild(JX.Mask._b);JX.$V.getDocument().setDim(JX.Mask._b);}++JX.Mask._a;},hide:function(){--JX.Mask._a;if(!JX.Mask._a){JX.DOM.remove(JX.Mask._b);JX.Mask._b=null;}}}});JX.install('Workflow',{construct:function(b,a){this.setURI(b);this.setData(a||{});},events:['error','finally','submit'],statics:{_c:[],newFromForm:function(b,a){var d=[].concat(JX.DOM.scry(b,'input'),JX.DOM.scry(b,'button'),JX.DOM.scry(b,'textarea'));for(var c=0;c<d.length;c++)if(d[c].disabled){delete d[c];}else d[c].disabled=true;var e=new JX.Workflow(b.getAttribute('action'),JX.copy(a||{},JX.DOM.serialize(b)));e.setMethod(b.getAttribute('method'));e.listen('finally',function(){for(var f=0;f<d.length;f++)d[f]&&(d[f].disabled=false);});return e;},newFromLink:function(a){var b=new JX.Workflow(a.href);return b;},_d:function(a){JX.Mask.show();JX.Workflow._c.push(a);},_e:function(){var a=JX.Workflow._c.pop();(a.getCloseHandler()||JX.bag)();a._f();JX.Mask.hide();},disable:function(){JX.Workflow._g=true;},_h:function(event){if(JX.Stratcom.pass())return;if(JX.Workflow._g)return;var e=event.getTarget();if(e.name=='__cancel__'||e.name=='__close__'){JX.Workflow._e();}else{var d=event.getNode('jx-dialog');var b=JX.DOM.serialize(d);b[e.name]=true;b.__wflow__=true;var a=JX.Workflow._c[JX.Workflow._c.length-1];var c=a.invoke('submit',{form:d,data:b});if(!c.getStopped()){a._f();a.setURI(d.getAttribute('action')||a.getURI()).setData(b).start();}}event.prevent();}},members:{_i:null,_j:false,_k:function(c){if(c&&(typeof c.redirect!='undefined')){JX.go(c.redirect,true);}else if(c&&c.dialog){this._d();this._i=JX.$N('div',{className:'jx-client-dialog'},JX.HTML(c.dialog));JX.DOM.listen(this._i,'click','tag:button',JX.Workflow._h);document.body.appendChild(this._i);var b=JX.$V.getDim(this._i);var e=JX.$V.getViewport();var d=JX.$V.getScroll();JX.$V((e.x-b.x)/2,d.y+100).setPos(this._i);try{JX.DOM.focus(JX.DOM.find(this._i,'button','__default__'));var inputs=JX.DOM.scry(this._i,'input').concat(JX.DOM.scry(this._i,'textarea'));var miny=Number.POSITIVE_INFINITY;var target=null;for(var ii=0;ii<inputs.length;++ii)if(inputs[ii].type!='hidden'){var p=JX.$V(inputs[ii]);if(p.y<miny){miny=p.y;target=inputs[ii];}}target&&JX.DOM.focus(target);}catch(a){}}else if(this.getHandler()){this.getHandler()(c);this._e();}},_d:function(){if(!this._j){this._j=true;JX.Workflow._d(this);}},_e:function(){if(this._j){this._j=false;JX.Workflow._e();}},_f:function(){if(this._i){JX.DOM.remove(this._i);this._i=null;}},start:function(){var c=this.getURI();var a=this.getMethod();var b=new JX.Request(c,JX.bind(this,this._k));b.setData(this.getData());b.setDataSerializer(this.getDataSerializer());if(a)b.setMethod(a);b.listen('finally',JX.bind(this,this.invoke,'finally'));b.listen('error',JX.bind(this,function(e){var d=this.invoke('error',e);if(d.getStopped())return;}));b.send();}},properties:{handler:null,closeHandler:null,data:null,dataSerializer:null,method:null,URI:null}});