[Pieforms-commit] SF.net SVN: pieforms: [260] pieforms-php5/trunk/src
Status: Alpha
Brought to you by:
oracleshinoda
From: <ora...@us...> - 2007-12-31 09:05:32
|
Revision: 260 http://pieforms.svn.sourceforge.net/pieforms/?rev=260&view=rev Author: oracleshinoda Date: 2007-12-31 01:05:31 -0800 (Mon, 31 Dec 2007) Log Message: ----------- Moved the javascript for handling jsform submission into pieforms.js, and made it work by AHAH. The major gain of this is that any form can display itself again when an error occurs in 100% the same way, regardless of whether the form is a jsform or not. The renderers don't have to provide javascript to describe how to insert error messages anymore either. The code is simpler and faster this way. So good benefits all around! Furthermore, I found that you can unset() a reference safely (i.e., it won't be unset everwhere else - only the reference will die). This is really handy for those foreach loops that take values by reference - you can unset them at the end of the loop and then know you can use the variable name again safely without random breakage happening later. Used this knowledge to fix one such 'interesting' bug that was occuring after jsform submission, where the last element in the form would be replaced by the second-to-last element. Modified Paths: -------------- pieforms-php5/trunk/src/pieform/renderers/div.php pieforms-php5/trunk/src/pieform/renderers/multicolumntable.php pieforms-php5/trunk/src/pieform/renderers/oneline.php pieforms-php5/trunk/src/pieform/renderers/table.php pieforms-php5/trunk/src/pieform.php pieforms-php5/trunk/src/static/core/pieforms.js Modified: pieforms-php5/trunk/src/pieform/renderers/div.php =================================================================== --- pieforms-php5/trunk/src/pieform/renderers/div.php 2007-12-31 02:03:42 UTC (rev 259) +++ pieforms-php5/trunk/src/pieform/renderers/div.php 2007-12-31 09:05:31 UTC (rev 260) @@ -68,26 +68,4 @@ return $result; }/*}}}*/ - -function pieform_renderer_div_get_js($id) {/*{{{*/ - $result = <<<EOF -// Given a message and form element name, should set an error on the element -function {$id}_set_error(message, element) { - element = '{$id}_' + element; - addElementClass(element + '_container', 'error'); - addElementClass(element, 'error'); - insertSiblingNodesAfter(element + '_container', DIV({'class': 'errmsg'}, message)); -} -function {$id}_remove_all_errors() { - forEach(getElementsByTagAndClassName('DIV', 'errmsg', '{$id}'), function(div) { - removeElement(div); - }); - forEach(getElementsByTagAndClassName(null, 'error', '{$id}'), function(div) { - removeElementClass(div, 'error'); - }); -} -EOF; - return $result; -}/*}}}*/ - ?> Modified: pieforms-php5/trunk/src/pieform/renderers/multicolumntable.php =================================================================== --- pieforms-php5/trunk/src/pieform/renderers/multicolumntable.php 2007-12-31 02:03:42 UTC (rev 259) +++ pieforms-php5/trunk/src/pieform/renderers/multicolumntable.php 2007-12-31 09:05:31 UTC (rev 260) @@ -43,43 +43,6 @@ $formrenderermct->set_form($form); }/*}}}*/ -function pieform_renderer_multicolumntable_get_js($id) {/*{{{*/ - return <<<EOF -function {$id}_set_error (message, element) { - element = '{$id}_' + element; - var parentRow = $(element + '_container').parentNode; - var nextRow = parentRow.nextSibling; - if (!(nextRow && hasElementClass(nextRow, 'errorRow'))) { - var errorRow = TR({'class': 'errorRow'}); - log(parentRow.cells.length); - for (var i = 0; i < parentRow.cells.length; i++) { - var attrs = null; - if (parentRow.cells[i].id) { - attrs = { - 'id': parentRow.cells[i].id.replace(/_container$/, '_error'), - 'class': 'error' - }; - } - appendChildNodes(errorRow, TD(attrs)); - } - insertSiblingNodesAfter($(element + '_container').parentNode, errorRow); - } - - appendChildNodes(element + '_error', message); - addElementClass(element, 'error'); - addElementClass( element + '_container', 'error'); -} -function {$id}_remove_all_errors() { - forEach(getElementsByTagAndClassName('TR', 'errorRow', '{$id}'), function(row) { - removeElement(row); - }); - forEach(getElementsByTagAndClassName(null, 'error', '{$id}'), function(item) { - removeElementClass(item, 'error'); - }); -} -EOF; -}/*}}}*/ - function pieform_renderer_multicolumntable_header() {/*{{{*/ global $formrenderermct; $formrenderermct = new FormRendererMultiColumnTable(); Modified: pieforms-php5/trunk/src/pieform/renderers/oneline.php =================================================================== --- pieforms-php5/trunk/src/pieform/renderers/oneline.php 2007-12-31 02:03:42 UTC (rev 259) +++ pieforms-php5/trunk/src/pieform/renderers/oneline.php 2007-12-31 09:05:31 UTC (rev 260) @@ -70,11 +70,4 @@ return $result; }/*}}}*/ -function pieform_renderer_oneline_get_js($id) {/*{{{*/ - return <<<EOF -function {$id}_remove_all_errors () {} -function {$id}_set_error () {} -EOF; -}/*}}}*/ - ?> Modified: pieforms-php5/trunk/src/pieform/renderers/table.php =================================================================== --- pieforms-php5/trunk/src/pieform/renderers/table.php 2007-12-31 02:03:42 UTC (rev 259) +++ pieforms-php5/trunk/src/pieform/renderers/table.php 2007-12-31 09:05:31 UTC (rev 260) @@ -102,26 +102,4 @@ return "</tbody></table>\n"; }/*}}}*/ -function pieform_renderer_table_get_js($id) {/*{{{*/ - $result = <<<EOF -function {$id}_set_error(message, element) { - element = $('{$id}_' + element + '_container'); - var container = getFirstElementByTagAndClassName('TD', null, element); - addElementClass(container, 'error'); - addElementClass(container.firstChild, 'error'); - insertSiblingNodesAfter(element, TR(null, TD({'colspan': 2, 'class': 'errmsg'}, message))); -} -function {$id}_remove_all_errors() { - forEach(getElementsByTagAndClassName('TD', 'errmsg', $('$id')), function(item) { - removeElement(item.parentNode); - }); - forEach(getElementsByTagAndClassName('TD', 'error', $('$id')), function(item) { - removeElementClass(item, 'error'); - removeElementClass(item.firstChild, 'error'); - }); -} -EOF; - return $result; -}/*}}}*/ - ?> Modified: pieforms-php5/trunk/src/pieform.php =================================================================== --- pieforms-php5/trunk/src/pieform.php 2007-12-31 02:03:42 UTC (rev 259) +++ pieforms-php5/trunk/src/pieform.php 2007-12-31 09:05:31 UTC (rev 260) @@ -228,22 +228,24 @@ } // Get references to all the elements in the form, excluding fieldsets - foreach ($this->data['elements'] as $name => &$_element) { - if (isset($_element['type']) && $_element['type'] == 'fieldset') { + foreach ($this->data['elements'] as $name => &$element) { + if (isset($element['type']) && $element['type'] == 'fieldset') { // Load the fieldset plugin as we know this form has one now $this->include_plugin('element', 'fieldset'); if ($this->get_property('template')) { self::info("Your form '$this->name' has a fieldset, but is using a template. Fieldsets make no sense when using templates"); } - foreach ($_element['elements'] as $subname => &$_subelement) { - $this->elementrefs[$subname] = &$_subelement; + foreach ($element['elements'] as $subname => &$subelement) { + $this->elementrefs[$subname] = &$subelement; } + unset($subelement); } else { - $this->elementrefs[$name] = &$_element; + $this->elementrefs[$name] = &$element; } } + unset($element); // Check that all elements have names compliant to PHP's variable naming policy // (otherwise things get messy later) @@ -344,6 +346,7 @@ } $element['name'] = $name; } + unset($element); // Check if the form was submitted, and if so, validate and process it $global = ($this->data['method'] == 'get') ? $_GET: $_POST; @@ -366,7 +369,7 @@ throw new PieformException('Cancel element "' . $element['name'] . '" has no page to go to'); } if ($this->data['jsform']) { - $this->json_reply(PIEFORM_CANCEL, $element['goto']); + $this->json_reply(PIEFORM_CANCEL, array('goto' => $element['goto']), false); } header('HTTP/1.1 303 See Other'); header('Location:' . $element['goto']); @@ -434,13 +437,16 @@ // If the form has been submitted by javascript, return json if ($this->data['jsform']) { - $errors = $this->get_errors(); - $json = array(); - foreach ($errors as $element) { - $json[$element['name']] = $element['error']; - } + // TODO: get error messages in a 'third person' type form to + // use here maybe? Would have to work for non js forms too. See + // the TODO file + //$errors = $this->get_errors(); + //$json = array(); + //foreach ($errors as $element) { + // $json[$element['name']] = $element['error']; + //} $message = $this->get_property('jserrormessage'); - $this->json_reply(PIEFORM_ERR, array('message' => $message, 'errors' => $json)); + $this->json_reply(PIEFORM_ERR, array('message' => $message)); } } }/*}}}*/ @@ -614,23 +620,36 @@ if ($outputformtags) { $result .= "</form>\n"; } + } - if ($this->data['jsform'] || $this->data['presubmitcallback']) { - $result .= '<script type="text/javascript">'; - if ($outputformtags) { - $result .= "\n" . $this->whichbutton_js(); + // Output the javascript to wire things up, but only if it is needed. The two cases where it is needed is when: + // 1) The form is a JS form that hasn't been submitted yet. When the + // form has been submitted the javascript from the first page load is + // still active in the documente + // 2) The form is NOT a JS form, but has a presubmitcallback + if (($this->data['jsform'] && !$this->submitted) + || (!$this->data['jsform'] && $this->data['presubmitcallback'])) { + $result .= '<script type="text/javascript">'; + $submitbuttons = array(); + foreach ($this->elementrefs as $element) { + if (!empty($element['submitelement'])) { + // TODO: might have to deal with cancel elements here too + $submitbuttons[] = $element['name']; } } - if ($this->data['jsform']) { - $result .= $this->submit_js(); - } - else if ($this->data['presubmitcallback'] && $outputformtags) { - $result .= 'connect(\'' . $this->name . '\', \'onsubmit\', ' - . 'function() { ' . $this->data['presubmitcallback'] . "('{$this->name}', {$this->name}_btn); });"; - } - if ($this->data['jsform'] || $this->data['presubmitcallback']) { - $result .= "\n</script>\n"; - } + + $data = json_encode(array( + 'name' => $this->name, + 'jsForm' => $this->data['jsform'], + 'submitButtons' => $submitbuttons, + 'preSubmitCallback' => $this->data['presubmitcallback'], + 'jsSuccessCallback' => $this->data['jssuccesscallback'], + 'jsErrorCallback' => $this->data['jserrorcallback'], + 'globalJsErrorCallback' => $this->data['globaljserrorcallback'], + 'postSubmitCallback' => $this->data['postsubmitcallback'], + )); + $result .= "new Pieform($data);\n"; + $result .= "</script>\n"; } return $result; @@ -788,149 +807,40 @@ } }/*}}}*/ - private function whichbutton_js() {/*{{{*/ - $result = "var {$this->name}_btn = null;\n"; - - $connecteventadded = false; - foreach ($this->elementrefs as $element) { - if (!empty($element['submitelement'])) { - if (!$connecteventadded) { - $result .= "addLoadEvent(function() {\n"; - $connecteventadded = true; - } - if (!empty($element['cancelelement'])) { - $cancelstr = 'cancel_'; - } - else { - $cancelstr = ''; - } - $result .= " connect($('{$cancelstr}{$this->name}_{$element['name']}'), 'onclick', function() { {$this->name}_btn = '{$cancelstr}{$this->name}_{$element['name']}'; });\n"; - } - } - if ($connecteventadded) { - $result .= "});\n"; - } - - return $result; - }/*}}}*/ - /** - * Builds the javascript for submitting the form. Note that the iframe is - * not hidden with display: none, as safari/konqueror/ns6 ignore things with - * display: none. Positioning it absolute and 'hidden' has the same effect - * without the breakage. + * Sends a message back to a jsform. + * + * The message can contain almost any data, although how it is used is up to + * the javascript callbacks. The message must contain a return code (the + * first parameter of this method. + * + * - The return code of the result. Either one of the PIEFORM_OK, + * PIEFORM_ERR or PIEFORM_CANCEL codes, or a custom error code at the + * choice of the application using pieforms + * - A message. This is just a string that can be used as a status message, + * e.g. 'Form failed submission' + * - HTML to replace the form with. By default, the form is built and used, + * but for example, you could replace the form with a "thank you" message + * after successful submission if you want */ - private function submit_js() {/*{{{*/ - $result = <<<EOF -connect($('{$this->name}'), 'onsubmit', function(e) { - if (typeof(tinyMCE) != 'undefined') { tinyMCE.triggerSave(); } - -EOF; - if (!empty($this->data['presubmitcallback'])) { - $result .= " {$this->data['presubmitcallback']}('{$this->name}', {$this->name}_btn);\n"; + public function json_reply($returncode, $data=array(), $replacehtml=null) {/*{{{*/ + if (is_string($data)) { + $data = array( + 'message' => $data, + ); } - $result .= <<<EOF - - var iframe = $('{$this->name}_iframe'); - $('{$this->name}').target = '{$this->name}_iframe'; - if (!iframe) { - iframe = createDOM('iframe', { - 'name': '{$this->name}_iframe', - 'id' : '{$this->name}_iframe', - 'style': 'position: absolute; visibility: hidden;' - }); - insertSiblingNodesAfter($('{$this->name}'), iframe); - - window.pieformHandler_{$this->name} = function(data) { - -EOF; - if (isset($this->data['processingstopcallback'])) { - $result .= " {$this->data['processingstopcallback']}('{$this->name}', {$this->name}_btn);\n"; + $data['returnCode'] = intval($returncode); + if ($replacehtml === null) { + $data['replaceHTML'] = $this->build(); } - - $result .= <<<EOF - evalJSONRequest(data); - if (data.returnCode == 0) { - {$this->name}_remove_all_errors(); - // The request completed successfully - -EOF; - if (!empty($this->data['jssuccesscallback'])) { - $result .= " {$this->data['jssuccesscallback']}('{$this->name}', data);\n"; + else if (is_string($replacehtml)) { + $data['replaceHTML'] = $replacehtml; } - $result .= <<<EOF - } - else { - if (data.returnCode == -2) { - window.location = data.message; - return; - } - - {$this->name}_remove_all_errors(); - if (data.message.errors) { - for (error in data.message.errors) { - {$this->name}_set_error(data.message.errors[error], error); - } - // @todo only output when fieldsets are present - forEach(getElementsByTagAndClassName('fieldset', 'collapsed', '{$this->name}'), function(fieldset) { - if (getFirstElementByTagAndClassName(null, 'error', fieldset)) { - removeElementClass(fieldset, 'collapsed'); - } - }); - } - - if (data.returnCode == -1) { - -EOF; - if (!empty($this->data['jserrorcallback'])) { - $result .= " {$this->data['jserrorcallback']}('{$this->name}', data);\n"; - } - $result .= <<<EOF - } - else { - -EOF; - if (!empty($this->data['globaljserrorcallback'])) { - $result .= " {$this->data['globaljserrorcallback']}('{$this->name}', data);\n"; - } - else { - $result .= " alert('Developer: got error code ' + data.returnCode - + ', either fix your form to not use this code or define a global js error handler');\n"; - } - $result .= <<<EOF - } - } - -EOF; - if (!empty($this->data['postsubmitcallback'])) { - $result .= " {$this->data['postsubmitcallback']}('{$this->name}', {$this->name}_btn);\n"; - } - - $result .= <<<EOF - {$this->name}_btn = null; - } - } - -EOF; - $result .= "});\n\n"; - $function = 'pieform_renderer_' . $this->data['renderer'] . '_get_js'; - if (!function_exists($function)) { - throw new PieformException('No renderer message function "' . $function . '"'); - } - - return $result . $function($this->name); - }/*}}}*/ - - public function json_reply($returncode, $message=null) {/*{{{*/ - $data = array( - 'returnCode' => intval($returncode), - 'message' => $message - ); $result = json_encode($data); echo <<<EOF -<html><head><script type="text/javascript">function sendResult() { parent.pieformHandler_{$this->name}($result); }</script></head><body onload="sendResult(); "></body></html> +<html><head><script type="text/javascript">function sendResult() { parent.pieformHandlers["{$this->name}"]($result); }</script></head><body onload="sendResult(); "></body></html> EOF; exit; }/*}}}*/ Modified: pieforms-php5/trunk/src/static/core/pieforms.js =================================================================== --- pieforms-php5/trunk/src/static/core/pieforms.js 2007-12-31 02:03:42 UTC (rev 259) +++ pieforms-php5/trunk/src/static/core/pieforms.js 2007-12-31 09:05:31 UTC (rev 260) @@ -1,11 +1,147 @@ /** - * Pieforms core javascript - * Author: Nigel McNie - * (C) 2006 Nigel McNie - * Released under the GNU GPL, see the COPYING file - * @todo pack this, provide a source version. Same with MochiKit + * Pieforms: Advanced web forms made easy + * Copyright (C) 2006-2008 Catalyst IT Ltd (http://www.catalyst.net.nz) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package pieform + * @subpackage static + * @author Nigel McNie <ni...@ca...> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL + * @copyright (C) 2006-2008 Catalyst IT Ltd http://catalyst.net.nz + * */ +window.pieformHandlers = {}; + +/** + * Handles the javascript side of pieforms - submitting the form via a hidden + * iframe and dealing with the result + */ +function Pieform(data) { + var self = this; + + this.init = function() { + connect(self.data.name, 'onsubmit', self.processForm); + + forEach(self.data.submitButtons, function(buttonName) { + connect(self.data.name + '_' + buttonName, 'onclick', function() { self.clickedButton = this; }); + }); + } + + this.processForm = function(e) { + // HACK: save any tinyMCE elements on the page. + // TODO: allow elements to export javascript to run at certain times - + // like now, when the form is being submitted + if (typeof(tinyMCE) != 'undefined') { tinyMCE.triggerSave(); } + + // Call the presubmit callback, if there is one + if (typeof(self.data.preSubmitCallback) == 'string' + && self.data.preSubmitCallback != "") { + window[self.data.preSubmitCallback]($(self.data.name), self.clickedButton, e); + } + + // If the form actually isn't a jsform - i.e. only a presubmithandler + // was defined - we stop here + if (!self.data.jsForm) { + return; + } + + // Ensure the iframe exists and make sure the form targets it + self.setupIframe(); + $(self.data.name).target = self.data.name + '_iframe'; + + window.pieformHandlers[self.data.name] = function(data) { + // If canceling the form, redirect away + if (data.returnCode == -2) { + alert('redirecting to ' + data.goto); + window.location = data.goto; + return; + } + + var tmp = DIV(); + tmp.innerHTML = data.replaceHTML; + // The first child node is the form tag. We replace the children of + // the current form tag with the new children. This prevents + // javascript references being lost + replaceChildNodes($(self.data.name), tmp.childNodes[0].childNodes); + + if (data.returnCode == 0) { + // Call the defined success callback, if there is one + if (typeof(self.data.jsSuccessCallback) == 'string' + && self.data.jsSuccessCallback != "") { + window[self.data.jsSuccessCallback]($(self.data.name), data); + } + else { + // TODO: work out what I'm going to do here... + if (typeof(data.message) == 'string' && data.message != '') { + alert(data.message); + } + } + } + else if (data.returnCode == -1) { + if (typeof(self.data.jsErrorCallback) == 'string' + && self.data.jsErrorCallback != '') { + window[self.data.jsErrorCallback]($(self.data.name), data); + } + } + else if (typeof(self.data.globalJsErrorCallback) == 'string' + && self.data.globalJsErrorCallback != '') { + window[self.data.globalJsErrorCallback]($(self.data.name), data); + } + else { + alert('Developer: got error code ' + data.returnCode + + ', either fix your form to not use this code or define ' + + 'a global js error handler'); + } + + // The post submit callback (for if the form succeeds or fails, but + // not for if it cancels) + if (typeof(self.data.postSubmitCallback) == 'string' + && self.data.postSubmitCallback != '') { + window[self.data.postSubmitCallback]($(self.data.name), self.clickedButton, e); + } + } + } + + this.setupIframe = function() { + var iframeName = self.data.name + '_iframe'; + if ($(iframeName)) { + self.iframe = $(iframeName); + } + else { + self.iframe = createDOM('iframe', { + 'name': iframeName, + 'id' : iframeName, + 'style': 'position: absolute; visibility: hidden;' + }); + insertSiblingNodesAfter(self.data.name, self.iframe); + } + } + + // A reference to the iframe that submissions are made through + this.iframe = null; + + // The button that was clicked to trigger the form submission + this.clickedButton = null; + + // Form configuration data passed from PHP + this.data = data; + + addLoadEvent(self.init); +} + // The resizable textarea code is based on the code from Drupal (http://drupal.org/) /** This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |