示例#1
0
 /**
  * @dataProvider detectRPCDataProvider
  */
 function testDetectRPC($invocationPath, $rpcInvocationPath, $expectIsRPC, $explanation)
 {
     $explanation .= str_pad("\nSimulated URL:", 40) . $invocationPath;
     $explanation .= str_pad("\nSimulated rpcInvocationPath:", 40) . $rpcInvocationPath;
     // FAKE RPC
     $fakeRPC = new WFRPC();
     $fakeRPC->setInvocationPath($rpcInvocationPath)->setTarget('foo')->setAction('bar');
     $_REQUEST = $fakeRPC->rpcAsParameters();
     $rpc = WFRPC::rpcFromRequest($invocationPath);
     if ($expectIsRPC) {
         $this->assertNotNull($rpc, $explanation);
     } else {
         $this->assertNull($rpc, $explanation);
     }
 }
示例#2
0
 /**
  * Initialize the named page. This will load the widget instances and prepare for manipulating the UI.
  *
  * Each page has two parts, the HTML template, and the page instances config file (also called the .yaml file).
  * On the filesystem, they are named <pageName>.tpl (the template file) and <pageName>.yaml (the config file).
  *
  * Once the instances are initialized and configured, the module will be given a chance to load default settings for the page via a callback.
  * This is the time to set up select lists, default values, etc.
  * The method name that will be called on the module is "<pageName>_PageDidLoad". Here is the prototype for that method:
  * void <pageName>_PageDidLoad($page, $parameters);
  *
  * The parameters argument is an associative array, with "name" => "value". The items that will be in the array are determined by the page's parameterList,
  * which is provided by the <pageName>_ParameterList() method, you can implement if your page needs parameters. This method should simply return a list of
  * strings, which will become the "names" passed into your _PageDidLoad method.
  *
  * Here's how the parameterization works... for each item in the array, first the PATH_INFO is mapped to the items. So, if your parameterList is:
  *
  * array('itemID', 'otherParameter')
  *
  * And the PATH_INFO is /12/moreData then the parameters passed in will be array('itemID' => '12', 'otherParameter' => 'moreData').
  * Any data that cannot be matched up will be passed with a NULL value.
  * Also, if there is a form submitted, then the values for the parameters will be replaced by the "value" of the outlets of the same name.
  * Thus, if your form has an "itemID" hidden field, the value from the form will supercede the value from PATH_INFO.
  *
  * After the _PageDidLoad call, the UI state from the request will be applied on top of the defaults.
  *
  * @todo Something is goofy with the detection of isRequestPage surrounding the action processor... seems to be getting called on the response page too. investiage.
  * @todo Rename this to executePage or something... this actually runs the whole page infrastructure!
  */
 function initPage($pageName)
 {
     WFLog::log("Initing page {$pageName}", WFLog::TRACE_LOG);
     if (!empty($this->pageName)) {
         throw new Exception("Page already inited with: {$this->pageName}. Cannot initPage twice.");
     }
     $this->pageName = $pageName;
     // look for page delegate
     $pageDelegateClassName = $this->module->moduleName() . '_' . $this->pageName;
     if (class_exists($pageDelegateClassName, false)) {
         $delegate = new $pageDelegateClassName();
         // look for case mismatch b/w instantiated name and name in URL; php class names are case-insensitive, so class_exists() can kinda false-positive on us here
         // the mistmatch of cases means that downstream users of the name (loading yaml files, etc) will unexpectedly fail.
         if ($pageDelegateClassName !== get_class($delegate)) {
             throw new WFRequestController_NotFoundException("Page {$this->pageName} not found.");
             // $this->pageName = substr(get_class($delegate), strlen($this->module->moduleName() . '_')); // upgrade name to correct for case
         }
         $this->setDelegate($delegate);
     }
     // calculate various file paths
     $basePagePath = $this->module->pathToPage($this->pageName);
     $yamlFile = $basePagePath . '.yaml';
     $instancesFile = $basePagePath . '.instances';
     $configFile = $basePagePath . '.config';
     if (file_exists($yamlFile)) {
         WFLog::log("Loading YAML config: {$pageName}.yaml", WFLog::TRACE_LOG);
         $yamlConfig = WFYaml::load($yamlFile);
         //print_r($yamlConfig);
         foreach ($yamlConfig as $id => $instanceManifest) {
             $this->initInstanceYAML($id, $instanceManifest);
         }
         // after all config info is loaded, certain widget types need to "update" things...
         // since we don't control the order of property loading (that would get way too complex) we just handle some things at the end of the loadConfig
         foreach ($this->instances as $viewId => $view) {
             $view->allConfigFinishedLoading();
         }
     } else {
         // parse instances file and instantiate all objects be graceful -- no need to have fatal error if there are no instances
         if (file_exists($instancesFile)) {
             include $instancesFile;
             foreach ($__instances as $id => $instanceManifest) {
                 $this->initInstancePHP($id, $instanceManifest);
             }
             // parse config file - for each instance, see if there is a config setup for that instance ID and apply it.
             // note: this will call allConfigFinishedLoading automatically
             $this->loadConfig($configFile);
         }
     }
     // let delegate know the page instances are ready
     $this->pageInstancesDidLoad();
     // restore UI state, if this is the requestPage
     // must happen AFTER config is loaded b/c some config options may affect how the widgets interpret the form data.
     // must happen BEFORE _PageDidLoad callback because that callback may need to access widget data, before it's available via bindings.
     if ($this->isRequestPage()) {
         $this->restoreState();
     }
     // the PARAMTERS are ONLY determined for the requestPage!
     // Calculate parameters
     $parameters = array();
     $parameterList = $this->parameterList();
     // get parameter definition from delegate
     if ($this->isRequestPage()) {
         WFLog::log("Parameterizing {$pageName}", WFLog::TRACE_LOG);
         if (count($parameterList) > 0) {
             // first map all items through from PATH_INFO
             // @todo Right now this doesn't allow DEFAULT parameter values (uses NULL). Would be nice if this supported assoc_array so we could have defaults.
             $invocationParameters = $this->module->invocation()->parameters();
             $defaultOpts = array('defaultValue' => NULL, 'greedy' => false);
             $i = 0;
             $lastI = count($parameterList) - 1;
             foreach ($parameterList as $k => $v) {
                 if (gettype($k) === 'integer') {
                     $opts = $defaultOpts;
                     $parameterKey = $v;
                 } else {
                     $parameterKey = $k;
                     if (is_array($v)) {
                         $opts = array_merge($defaultOpts, $v);
                     } else {
                         $opts = $defaultOpts;
                         $opts['defaultValue'] = $v;
                     }
                 }
                 if (isset($invocationParameters[$i])) {
                     // handle greedy
                     if ($i === $lastI and $opts['greedy'] === true and count($invocationParameters) > count($parameterList)) {
                         $parameters[$parameterKey] = join('/', array_slice($invocationParameters, $i));
                     } else {
                         $parameters[$parameterKey] = $invocationParameters[$i];
                     }
                 } else {
                     $parameters[$parameterKey] = isset($_REQUEST[$parameterKey]) ? $_REQUEST[$parameterKey] : $opts['defaultValue'];
                 }
                 $i++;
             }
             // then over-ride with from form, if one has been submitted
             if ($this->hasSubmittedForm()) {
                 foreach ($parameterList as $id) {
                     if (!$this->hasOutlet($id)) {
                         continue;
                     }
                     // see if there is an instance of the same name in the submitted form
                     $instance = $this->outlet($id);
                     if ($instance instanceof WFWidget) {
                         // walk up looking for parent
                         $parent = $instance->parent();
                         do {
                             if ($parent and $parent instanceof WFForm and $parent->name() == $this->submittedFormName()) {
                                 $parameters[$id] = $this->outlet($id)->value();
                                 break;
                             }
                             if (!is_object($parent)) {
                                 throw new Exception("Error processing parameter overload for parameter id: '{$id}': found widget of same id, but cannot determine the form that it belongs to. Are you sure that widget id: '{$id}' is in a WFForm?");
                             }
                             $parent = $parent->parent();
                         } while ($parent);
                     }
                 }
             }
         }
     } else {
         WFLog::log("Skipping Parameterization for {$pageName}", WFLog::TRACE_LOG);
         if (count($parameterList) > 0) {
             // NULL-ify all params
             for ($i = 0; $i < count($parameterList); $i++) {
                 $parameters[$parameterList[$i]] = NULL;
             }
         }
     }
     // save completed parameters
     $this->parameters = $parameters;
     // inform delegate that params are ready
     $this->parametersDidLoad();
     // parametersDidLoad may have affected the arrayControllers
     $this->createDynamicWidgets();
     // restore UI state AGAIN so that any controls created dynamically can update their values based on the UI state.
     if ($this->isRequestPage()) {
         $this->restoreState();
     }
     // now that the UI is set up (instantiated), it's time to propagate the values from widgets to bound objects if this is the requestPage!
     // then, once the values are propagated, we should call the action handler for the current event, if there is one.
     if ($this->isRequestPage()) {
         // let delegate know that we're about to push bindings - this is effectively a statement of "we're in 'postback', deal with it if you must"
         $this->willPushBindings();
         // willPushBindings may have affected the arrayControllers
         $this->createDynamicWidgets();
         // push values of bound properties back to their bound objects
         $this->module->requestPage()->pushBindings();
         // Determine action: do we need to call the noAction handler?
         $rpc = NULL;
         if ($this->hasSubmittedForm()) {
             // look for action in the form data
             $rpc = WFRPC::rpcFromRequest($this->module()->invocation()->invocationPath());
             if (!$rpc) {
                 // look for the submit button;
                 // look up the instance ID for the specified action... look for "action|<actionOutletID>" in $_REQUEST...
                 // but need to skip the _x and _y fields submitted with image submit buttons
                 $actionOutletID = NULL;
                 foreach ($_REQUEST as $name => $value) {
                     if (strncmp("action|", $name, 7) == 0 and !in_array(substr($name, -2, 2), array('_x', '_y'))) {
                         list(, $actionOutletID) = explode('|', $name);
                         break;
                     }
                 }
                 // if there is no button found in the parameters, we ask the WFForm what the default submit button is
                 if (!$actionOutletID) {
                     $form = $this->outlet($this->submittedFormName());
                     $actionOutletID = $form->defaultSubmitID();
                     WFLog::log("Form submitted, but no action button detected. Using default button: {$actionOutletID}", WFLog::TRACE_LOG);
                 }
                 // call the ACTION handler for the page, if there is an action.
                 if ($actionOutletID) {
                     try {
                         $action = $this->outlet($actionOutletID)->submitAction();
                         $action->rpc()->setArguments(array($actionOutletID, 'click'));
                         $rpc = $action->rpc();
                     } catch (Exception $e) {
                         throw new WFException("Could not find form button (outlet) for current action: {$actionOutletID}. Make sure that you don't have nested forms!");
                     }
                 } else {
                     WFLog::log("No action occurred (no action specified in form data)", WFLog::WARN_LOG);
                 }
             }
         } else {
             // look for action in Params
             // new-school WFAction stuff;
             $rpc = WFRPC::rpcFromRequest($this->module()->invocation()->invocationPath());
         }
         // deal with action
         if ($rpc) {
             $shouldRun = false;
             if ($this->hasSubmittedForm()) {
                 if ($rpc->runsIfInvalid() or !$rpc->runsIfInvalid() and $this->formIsValid()) {
                     if ($rpc->runsIfInvalid()) {
                         $this->setIgnoreErrors(true);
                         // runsIfInvalid by default implies ignoreErrors
                     }
                     $shouldRun = true;
                 } else {
                     if ($rpc->isAjax()) {
                         $this->sendPageErrorsOverAjax();
                     }
                 }
                 if ($shouldRun === false) {
                     $this->willNotRunAction();
                 }
             } else {
                 $shouldRun = true;
             }
             if ($shouldRun) {
                 try {
                     $rpc->execute($this);
                 } catch (WFErrorCollection $e) {
                     // Automagically map all errors for keys to widgets with the same ID
                     // note that propagateErrorsForKey* functions may prune errors
                     // from the original WFErrorCollection before we get here.
                     foreach ($this->instances as $id => $obj) {
                         if ($e->hasErrorsForKey($id)) {
                             $this->propagateErrorsForKeyToWidget($e, $id, $obj, true);
                         }
                     }
                     // add all remaining errors to the general error display
                     $this->addErrors($e->allErrors());
                 }
                 if ($rpc->isAjax() and count($this->errors())) {
                     $this->sendPageErrorsOverAjax();
                 }
             }
         } else {
             $this->noAction();
         }
         // action/noAction may have affecting the arrayControllers
         $this->createDynamicWidgets();
     }
 }
示例#3
0
文件: WFRPC.php 项目: ardell/phocoa
 /**
  * Add an internal RPC object for ServerAction and AjaxAction.
  *
  * @param boolean Is this an ajax RPC?
  * @return object WFAction The current WFAction instance, for fluent configuration.
  */
 protected function addRPC($isAjax = false)
 {
     if (!$this->rpc) {
         $this->rpc = WFRPC::RPC();
         $this->rpc->setTarget(WFRPC::TARGET_PAGE);
         $this->rpc->setIsAjax($isAjax);
     }
     return $this;
 }
示例#4
0
    function render($blockContent = NULL)
    {
        $loader = WFYAHOO_yuiloader::sharedYuiLoader();
        // jquery
        $loader->addModule('jquery', 'js', NULL, 'http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.js', NULL, NULL, NULL, NULL);
        // jquery-ui
        $loader->addModule('jqueryui-css', 'css', NULL, 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css', NULL, NULL, NULL, NULL);
        $loader->addModule('jqueryui', 'js', NULL, 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.js', array('jquery', 'jqueryui-css'), NULL, NULL, NULL);
        // query-file-uploader
        $loader->addModule('jquery-file-uploader', 'js', NULL, $this->getWidgetWWWDir() . '/jquery.fileupload.js', array('jquery'), NULL, NULL, NULL);
        // and the UI
        $loader->addModule('jquery-file-uploader-ui-css', 'css', NULL, $this->getWidgetWWWDir() . '/jquery.fileupload-ui.css', NULL, NULL, array('jqueryui-css'), NULL);
        $loader->addModule('jquery-file-uploader-ui', 'js', NULL, $this->getWidgetWWWDir() . '/jquery.fileupload-ui.js', array('jquery-file-uploader', 'jqueryui', 'jquery-file-uploader-ui-css'), NULL, NULL, NULL);
        $loader->yuiRequire('jquery-file-uploader-ui');
        // @todo In future this should not need to be a WFForm subclass; should be able to drop it in a form anywhere.
        //$form = $this->getForm();
        //if (!$form) throw new WFException("WFHTML5_Uploader must be a child of a WFForm.");
        $form = $this;
        // craft a WFRPC that we can insert into the form stream to have our callback fire
        $rpc = WFRPC::RPC()->setInvocationPath($this->page->module()->invocation()->invocationPath())->setTarget('#page#' . $this->id)->setAction('_handleAsyncSingleUpload')->setForm($this)->setIsAjax(true);
        $uploadFormData = json_encode($rpc->rpcAsParameters($this));
        // figure out vars for drop-in into HTML block
        $sequentialUploads = var_export($this->maxConcurrentUploads == 1, true);
        $autoupload = var_export($this->autoupload, true);
        $fileInputName = "{$this->getInputFileName()}[]";
        // HTML
        $formInnardsHTML = <<<END
                {$blockContent}
                <input type="file" name="{$fileInputName}" multiple>
                <button type="submit" name="action|{$this->id}">Upload</button>
                <div class="file_upload_label">Click or Drop to Upload</div>
END;
        $html = parent::render($formInnardsHTML);
        $maxUploadBytesJSON = WFJSON::encode($this->maxUploadBytes);
        // progress indicators after form since the blueimp plugin takes over the entire form area for drag-n-drop
        $html .= <<<END
<div id="{$this->id}_progressAll" style="display: none;"></div>
<table id="{$this->id}_table" style="display: none;"></table>
END;
        $withJqueryJS = <<<END
function() {
    jQuery.noConflict();
    jQuery(function () {
        window.uploader = jQuery('#{$form->id()}').fileUploadUI({
            formData: {$uploadFormData},
            sequentialUploads: {$sequentialUploads},
            beforeSend: function (event, files, index, xhr, handler, callBack) {
                document.fire ('WFHTML5_Uploader:uploadStart');
                jQuery('#{$this->id}_table, #{$this->id}_progressAll').show();
                var fileSize = files[index].fileSize ? files[index].fileSize : files[index].size  // Firefox calls it file.size instead of file.fileSize
                if ({$maxUploadBytesJSON} && fileSize > {$maxUploadBytesJSON})
                {
                    var json = {
                        'name': files[index].name,
                        'description': ('File exceeds maximum file size of ' + {$maxUploadBytesJSON} + ' bytes.'),
                        'uploadOK': false
                    };
                    handler.downloadRow = handler.buildDownloadRow(json, handler);
                    handler.replaceNode(handler.uploadRow, handler.downloadRow, null);
                    return;
                }
                if ({$autoupload})
                {
                    callBack();
                }
            },
            progressAllNode: jQuery('#{$this->id}_progressAll'),
            uploadTable: jQuery('#{$this->id}_table'),
            onCompleteAll: function() {
                var autoRedirectToUrlOnCompleteAll = '{$this->autoRedirectToUrlOnCompleteAll}';
                if (autoRedirectToUrlOnCompleteAll)
                {
                    document.fire ('WFHTML5_Uploader:uploadAllComplete');
                    window.location.href = autoRedirectToUrlOnCompleteAll;
                }
            },
            buildUploadRow: function (files, index) {
                return jQuery('<tr>' +
                        '<td width="175">' + files[index].name + '<\\/td>' +
                        '<td width="1">&nbsp;<\\/td>' +
                        '<td width="250">&nbsp;<\\/td>' +
                        '<td width="16" class="file_upload_cancel">' +
                            '<button class="ui-state-default ui-corner-all" title="Cancel">' +
                            '<span class="ui-icon ui-icon-cancel">Cancel<\\/span>' +
                            '<\\/button><\\/td>' +
                        '<td class="file_upload_progress" style="width: 160px"><div><\\/div><\\/td>' +
                        '<\\/tr>');
            },
            buildDownloadRow: function (file, handler) {
                var thumbHTML = '&nbsp;';
                if (file.thumb)
                {
                    thumbHTML = '<img s'+'rc="'+file.thumb+'" style="float: right;"/>';
                }
                return jQuery('<tr>' +
                    '<td width="175">' + file.name + '<\\/td>' +
                    '<td width="' + (thumbHTML ? 100 : 1) + '">' + thumbHTML + '<\\/td>' +
                    '<td width="250">' + file.description + '<\\/td>' + 
                    '<td width="16"><span class="ui-icon ' + (file.uploadOK ? 'ui-icon-check' : 'ui-icon-alert') + '"><\\/span><\\/td>' + 
                    '<td><\\/td>' + 
                    '<\\/tr>');
            }
        });
    });
}
END;
        $bootstrapJS = $loader->jsLoaderCode($withJqueryJS);
        $html .= <<<END
<script> 
{$bootstrapJS}
</script> 
END;
        return $html;
    }
示例#5
0
    function render($blockContent = NULL)
    {
        // craft a WFRPC that we can insert into the form stream to have our callback fire
        $rpc = WFRPC::RPC()->setInvocationPath($this->page->module()->invocation()->invocationPath())->setTarget('#page#' . $this->id)->setAction('handleUploadedFile')->setForm($this)->setIsAjax(true);
        return '
<applet name="postlet" code="Main.class" archive="' . $this->getWidgetWWWDir() . '/postlet.jar" width="305" height="200" mayscript="mayscript">
    <param name = "uploadparametername"      value = "' . $this->id . '" />
    <param name = "additionalformparameters" value = "' . http_build_query($rpc->rpcAsParameters($this), '', '&amp;') . '" />

    ' . ($this->dropimage ? '<param name = "dropimage"       value = "' . $this->baseurl . $this->dropimage . '"/>' : NULL) . '
    ' . ($this->dropimageupload ? '<param name = "dropimageupload"       value = "' . $this->baseurl . $this->dropimageupload . '"/>' : NULL) . '

    <param name = "maxthreads"      value = "' . $this->maxthreads . '" />
    <param name = "destination"     value = "' . $this->baseurl . '/' . $this->page->module()->invocation()->invocationPath() . '" />
    ' . ($this->backgroundcolour ? '<param name = "backgroundcolour" value = "' . $this->backgroundcolour . '" />' : NULL) . '
    ' . ($this->tableheaderbackgroundcolour ? '<param name = "tableheaderbackgroundcolour" value = "' . $this->tableheaderbackgroundcolour . '" />' : NULL) . '
    ' . ($this->tableheadercolour ? '<param name = "tableheadercolour" value = "' . $this->tableheadercolour . '" />' : NULL) . '
    <param name = "warnmessage" value = "' . ($this->warnmessage ? 'true' : 'false') . '" />
    <param name = "autoupload" value = "' . ($this->autoupload ? 'true' : 'false') . '" />
    <param name = "helpbutton" value = "' . ($this->helpbutton ? 'true' : 'false') . '" />
    <param name = "removebutton" value = "' . ($this->removebutton ? 'true' : 'false') . '" />
    <param name = "addbutton" value = "' . ($this->addbutton ? 'true' : 'false') . '" />
    <param name = "uploadbutton" value = "' . ($this->uploadbutton ? 'true' : 'false') . '" />
    <param name = "fileextensions" value = "JPEG Image Files,jpg,jpeg" />
    ' . ($this->endpage ? '<param name = "endpage" value = "' . $this->baseurl . $this->endpage . '" />' : NULL) . '
</applet>
<script type="text/javascript" src="' . WFWebApplication::webDirPath(WFWebApplication::WWW_DIR_FRAMEWORK) . '/js/embeddedcontent_min.js" defer="defer"></script>
        ';
    }
 function initJS($blockContent)
 {
     // craft a WFRPC that we can insert into the form stream to have our callback fire
     $rpc = WFRPC::RPC()->setInvocationPath($this->page->module()->invocation()->invocationPath())->setTarget('#page#' . $this->id)->setAction('handleUploadedFile')->setForm($this->getForm())->setRunsIfInvalid(true)->setIsAjax(true);
     $html = "\n        PHOCOA.widgets.{$this->id}.status = { 'READY': 0, 'UPLOADING': 1, 'COMPLETE': 2};\n        PHOCOA.widgets.{$this->id}.summaryStatus = PHOCOA.widgets.{$this->id}.status.READY;\n        PHOCOA.widgets.{$this->id}.emptyFileListDisplay = function() {\n            PHOCOA.widgets.{$this->id}.filesToUploadTracker = {};\n            PHOCOA.widgets.{$this->id}.filesToUploadTotalBytes = 0;\n            \$('{$this->id}_fileList').update('').hide();\n        };\n        PHOCOA.widgets.{$this->id}.makePrettySize = function(sz, decimals) {\n            if (typeof decimals === 'undefined')\n            {\n                decimals = 2;\n            }\n            var suffixes = ['Bytes','KB','MB','GB','TB'];\n            var i = 0;\n\n            while (sz >= 1024 && (i < suffixes.length - 1)){\n                sz /= 1024;\n                i++;\n            }\n            return Math.round(sz*Math.pow(10,decimals))/Math.pow(10,decimals) + ' '  + suffixes[i];\n        };\n        PHOCOA.widgets.{$this->id}.init = function() {\n            PHOCOA.widgets.{$this->id}.emptyFileListDisplay();   // initialize\n\n            YAHOO.util.Event.onDOMReady(function () { \n                var uiLayer = YAHOO.util.Dom.getRegion('{$this->id}_browseTrigger');\n                var overlay = YAHOO.util.Dom.get('{$this->id}');\n                YAHOO.util.Dom.setStyle(overlay, 'width', uiLayer.width + 'px');\n                YAHOO.util.Dom.setStyle(overlay, 'height', uiLayer.height + 'px');\n            });\n\n            YAHOO.widget.Uploader.SWFURL = '" . WFYAHOO_yuiloader::sharedYuiLoader()->localYUI() . "uploader/assets/uploader.swf'; \n            var uploader = new YAHOO.widget.Uploader('{$this->id}');\n            PHOCOA.runtime.addObject(uploader, '{$this->id}');\n\n            // can't customize until the SWF is ready\n            uploader.addListener('contentReady', function() {\n                uploader.setAllowMultipleFiles(" . ($this->allowMultiple ? 'true' : 'false') . ");\n                uploader.setSimUploadLimit(1);\n            });\n\n            \$('{$this->id}_uploadTrigger').observe('click', function() {\n                PHOCOA.runtime.getObject('{$this->id}').uploadAll('" . $rpc->url() . "', 'POST', " . WFJSON::encode($rpc->rpcAsParameters()) . ", '{$this->id}');\n            });\n\n            uploader.addListener('fileSelect', function(e) {\n                // fileSelect sends ALL files tracked by flash, not just the ones added in the most recent file select dialog\n                PHOCOA.widgets.{$this->id}.emptyFileListDisplay();\n                \$('{$this->id}_fileList').show();\n\n                var files = \$H(e.fileList).values();\n\n                files.pluck('id').each(function(o) {\n                    PHOCOA.widgets.{$this->id}.filesToUploadTracker[o] = {\n                        id: e.fileList[o].id,\n                        name: e.fileList[o].name,\n                        size: e.fileList[o].size,\n                        sizeProgress: 0,\n                        status: PHOCOA.widgets.{$this->id}.status.READY,\n                        error: false,\n                        resultMessage: null\n                        };\n                });\n                ";
     if ($this->maxUploadBytes !== NULL) {
         $html .= "\n                var tooBig = [];\n                var justRight = {};\n                \$H(PHOCOA.widgets.{$this->id}.filesToUploadTracker).values().each(function(o) {\n                    if (o.size > {$this->maxUploadBytes})\n                    {\n                        PHOCOA.runtime.getObject('{$this->id}').removeFile(o.id);\n                        tooBig.push(o);\n                    }\n                    else\n                    {\n                        justRight[o.id] = o;\n                    }\n                });\n                PHOCOA.widgets.{$this->id}.filesToUploadTracker = justRight;\n                if (tooBig.length)\n                {\n                    alert('The following files will be skipped because they are more than ' + PHOCOA.widgets.{$this->id}.makePrettySize({$this->maxUploadBytes}) + \":\\n- \" + tooBig.pluck('name').join(\"\\n- \"));\n                }\n                ";
     }
     $html .= "\n                \$H(PHOCOA.widgets.{$this->id}.filesToUploadTracker).values().pluck('size').each(function(o) {\n                    PHOCOA.widgets.{$this->id}.filesToUploadTotalBytes += o;\n                });\n                PHOCOA.widgets.{$this->id}.updateProgress();\n            });\n            uploader.addListener('uploadStart', function(e) {\n                PHOCOA.widgets.{$this->id}.summaryStatus = PHOCOA.widgets.{$this->id}.status.UPLOADING;\n                PHOCOA.widgets.{$this->id}.updateProgress();\n                PHOCOA.widgets.{$this->id}.filesToUploadTracker[e.id].status = PHOCOA.widgets.{$this->id}.status.UPLOADING;\n            });\n            uploader.addListener('uploadProgress', function(e) {\n                PHOCOA.widgets.{$this->id}.filesToUploadTracker[e.id].sizeProgress = e.bytesLoaded;\n                PHOCOA.widgets.{$this->id}.updateProgress();\n            });\n            uploader.addListener('uploadError', function(e) {\n                PHOCOA.widgets.{$this->id}.filesToUploadTracker[e.id].error = true;\n                PHOCOA.widgets.{$this->id}.filesToUploadTracker[e.id].resultMessage = e.status;\n                PHOCOA.widgets.{$this->id}.filesToUploadTracker[e.id].status = PHOCOA.widgets.{$this->id}.status.COMPLETE;\n                PHOCOA.widgets.{$this->id}.updateProgress();\n            });\n            uploader.addListener('uploadComplete', function(e) {\n                PHOCOA.widgets.{$this->id}.filesToUploadTracker[e.id].sizeProgress = PHOCOA.widgets.{$this->id}.filesToUploadTracker[e.id].size;\n                PHOCOA.widgets.{$this->id}.filesToUploadTracker[e.id].status = PHOCOA.widgets.{$this->id}.status.COMPLETE;\n                PHOCOA.widgets.{$this->id}.updateProgress();\n\n                // are we done with ALL uploads?\n                if (\$H(PHOCOA.widgets.{$this->id}.filesToUploadTracker).values().all(function(o) { return o.sizeProgress === o.size; }))\n                {\n                    PHOCOA.widgets.{$this->id}.summaryStatus = PHOCOA.widgets.{$this->id}.status.COMPLETE;\n                    \$('{$this->id}_progress').update('Upload complete.');\n                    PHOCOA.runtime.getObject('{$this->id}').clearFileList();    // remove files from flash\n                    //PHOCOA.widgets.{$this->id}.emptyFileListDisplay();\n                    document.fire('WFYAHOO_widget_Uploader:allUploadsComplete', { uploadId: '{$this->id}' });\n                    if (" . WFJSON::encode(!empty($this->continueURL)) . ")\n                    {\n                        window.location = '{$this->continueURL}';\n                    }\n                }\n            });\n        };\n        PHOCOA.widgets.{$this->id}.updateProgress = function() {\n            // update summary progress bar\n            var allFilesToUpload = \$H(PHOCOA.widgets.{$this->id}.filesToUploadTracker).values();\n\n            if (allFilesToUpload.any(function(o) { return o.status !== PHOCOA.widgets.{$this->id}.status.READY; }))\n            {\n                var uploadProgressBytes = 0;\n                allFilesToUpload.pluck('sizeProgress').each( function(o) {\n                    uploadProgressBytes += o;\n                });\n                var msg = 'Upload progress: ' + Math.round(uploadProgressBytes*100 / PHOCOA.widgets.{$this->id}.filesToUploadTotalBytes) + '%';\n                \$('{$this->id}_progress').update(msg);\n            }\n\n            // Update per-file progress\n            // Sort by amount of upload left, descending. makes it easy to spot what's left and problems.\n            allFilesToUpload.sort( function(a,b) { return a.size - a.sizeProgress < b.size - b.sizeProgress } );\n            \$('{$this->id}_fileList').update('<strong>' + allFilesToUpload.length + ' file(s) selected:</strong><table><tr><th>Name</th><th nowrap>Size</th><th nowrap>Status</th></tr>' + allFilesToUpload.collect(function(o) { \n                                                                                                                            var msg = '<tr>';\n                                                                                                                            msg += '<td>' + o.name + '</td>';\n                                                                                                                            msg += '<td>' + PHOCOA.widgets.{$this->id}.makePrettySize(o.size) + '</td>';\n                                                                                                                            msg += '<td>';\n                                                                                                                            switch (o.status) {\n                                                                                                                                case PHOCOA.widgets.{$this->id}.status.READY:\n                                                                                                                                    break;\n                                                                                                                                case PHOCOA.widgets.{$this->id}.status.UPLOADING:\n                                                                                                                                    var pct = Math.round(100 * o.sizeProgress / o.size);\n                                                                                                                                    msg += ' progress: ' + pct + '%';\n                                                                                                                                    break;\n                                                                                                                                case PHOCOA.widgets.{$this->id}.status.COMPLETE:\n                                                                                                                                    if (o.error)\n                                                                                                                                    {\n                                                                                                                                        msg += ' Error uploading: ' + o.resultMessage;\n                                                                                                                                    }\n                                                                                                                                    else\n                                                                                                                                    {\n                                                                                                                                        msg += ' Uploaded.';\n                                                                                                                                    }\n                                                                                                                                    break;\n                                                                                                                            }\n                                                                                                                            msg += '</td>';\n                                                                                                                            msg += '</tr>';\n                                                                                                                            return msg;\n                                                                                                                        }).join('') + '</table>');\n        };\n        ";
     return $html;
 }