91370f4fa98d1691fe08f1a9133d42de33c5246a

Author: AD7six

Date: 2009-04-03 12:20:54 +0200

changing htmlspecialchars to h() calls for charset consistency minor view changes related to correcting/enabling dialog/popup js It should now be possible to submit a form in a dialog, and for the same dialog to recieve the content Whilst editing/adding content - have an open dialog that persists until you're finished

diff --git a/views/comments/index.ctp b/views/comments/index.ctp index 851ab87..a996ed3 100644 --- a/views/comments/index.ctp +++ b/views/comments/index.ctp @@ -1,11 +1,13 @@ +<?php if (empty($this->params['isAjax'])) : ?> <h2><?php if (isset($node)) { - echo $html->link(sprintf(__('Comments: %1$s', true), htmlspecialchars($node['Revision']['title'])), array('id' => $this->params['id'])); + echo $html->link(sprintf(__('Comments: %1$s', true), h($node['Revision']['title'])), array('id' => $this->params['id'])); } else { __('Recent Comments'); } ?></h2> <?php +endif; if (!$data) { echo '<p>' . __('No Comments yet!', true) . '</p>'; } else { @@ -15,7 +17,7 @@ if (!$data) { } if (isset($node)) { echo '<div class="comment"><p class="commenttitle"><em>'; - echo $html->link(__('Add a comment', true), am($this->passedArgs, array('controller' => 'comments', 'action' => 'add')), array('class' => 'popout')); + echo $html->link(__('Add a comment', true), am($this->passedArgs, array('controller' => 'comments', 'action' => 'add'))); echo '</em></p></div>'; } $html->meta('rss', $html->url($this->passedArgs) . '.rss', array('title' => __('This page as a feed', true)), false); diff --git a/views/comments/rss/index.ctp b/views/comments/rss/index.ctp index 50d874d..3f63c06 100755 --- a/views/comments/rss/index.ctp +++ b/views/comments/rss/index.ctp @@ -11,7 +11,7 @@ return; } return array( - 'title' => $row['Node']['sequence'] . ' ' . $row['Revision']['title'] . ' - ' . $html->clean(htmlspecialchars($row['Comment']['title'])), + 'title' => $row['Node']['sequence'] . ' ' . $row['Revision']['title'] . ' - ' . $html->clean(h($row['Comment']['title'])), 'link' => array('controller' => 'comments', 'action' => 'index', $row['Comment']['node_id'], 'lang' => $row['Comment']['lang'], '#' => "comment_{$row['Comment']['id']}"), 'description' => $_this->element('comment', array('data' => $row, 'fixedDates' => true)), diff --git a/views/elements/comment.ctp b/views/elements/comment.ctp index a60527d..6cb14eb 100755 --- a/views/elements/comment.ctp +++ b/views/elements/comment.ctp @@ -30,6 +30,6 @@ if ($this->action == 'recent') { echo htmlspecialchars($title); echo "</p>"; echo "<div class=\"commentbody\">"; -echo '<p>' . implode(explode("\n", htmlspecialchars($body)), '</p><p>') . '</p>'; +echo '<p>' . implode(explode("\n", h($body)), '</p><p>') . '</p>'; echo "</div>"; echo "</div>";?> \ No newline at end of file diff --git a/views/elements/content_form_note.ctp b/views/elements/content_form_note.ctp index c02ca35..0fc06f8 100644 --- a/views/elements/content_form_note.ctp +++ b/views/elements/content_form_note.ctp @@ -4,7 +4,7 @@ <li><?php echo sprintf( __('Please review %1$s to ensure consistency.', true), $html->link(__('the guidelines for submitting to the Cookbook', true), - array('controller' => 'nodes', 'action' => 'view', '482')) + array('controller' => 'nodes', 'action' => 'view', '482'), array('class' => 'popout')) ) ?></li> <?php if ($this->action == 'add' && $this->params['lang'] !== Configure::read('Languages.default')): ?> <li><?php echo sprintf( diff --git a/views/elements/login.ctp b/views/elements/login.ctp index 0525cfc..51cb64f 100644 --- a/views/elements/login.ctp +++ b/views/elements/login.ctp @@ -1,2 +1,17 @@ -<?php /* SVN FILE: $Id: login.ctp 600 2008-08-07 17:55:23Z AD7six $ */ -include(APP . 'plugins' . DS . 'users' . DS . 'views' . DS . 'users' . DS . 'login.ctp'); ?> \ No newline at end of file +<div class="login"> +<?php + echo $form->create('User', array('action' => 'login')); + echo $form->hidden('redirect', array('value' => $session->read('Auth.redirect'))); +?> + <fieldset> + <legend><?php __('Login') ?></legend> +<?php + echo $form->input('username'); + $after = '<p>' . $html->link(__('Forgot your password?', true), array('admin'=> false, 'action' => 'reset')) .'</p>'; + echo $form->input('password', array('after' => $after)); +?> + </fieldset> +<?php + echo $form->end(__('Login', true)); +?> +</div> \ No newline at end of file diff --git a/views/elements/node_options.ctp b/views/elements/node_options.ctp index bb78821..e70144f 100644 --- a/views/elements/node_options.ctp +++ b/views/elements/node_options.ctp @@ -17,7 +17,7 @@ if ($data['Node']['depth'] >= $viewAllLevel) { => 'dialog')); } if ($data['Node']['comment_level'] <= $auth['User']['Level'] && $this->layout == 'default') { - $out[] = $html->link(sprintf(__('Comments (%1$s)', true), count($data['Comment'])), array('controller' => 'comments', 'action' => 'index', $data['Node']['id']), array('class' => 'dialog')); + $out[] = $html->link(sprintf(__('Comments (%1$s)', true), count($data['Comment'])), array('controller' => 'comments', 'action' => 'index', $data['Node']['id']), array('title' => sprintf(__('Comments for %1$s', true), $data['Revision']['title']), 'class' => 'dialog')); } $flags = explode(',', trim($data['Revision']['flags'])); $flagLis = ''; diff --git a/views/elements/preview.ctp b/views/elements/preview.ctp index 931357a..fbbc419 100644 --- a/views/elements/preview.ctp +++ b/views/elements/preview.ctp @@ -25,7 +25,7 @@ if (empty($errors['Revision'])) { <fieldset> <?php echo $previewText ?> <div id='preview'> - <h2><?php echo htmlspecialchars($data['Revision']['title']) ?> </h2> + <h2><?php echo h($data['Revision']['title']) ?> </h2> <div class="view"><?php if (isset($highlight)) { echo $highlight->auto($data['Revision']['content']); diff --git a/views/layouts/ajax.ctp b/views/layouts/ajax.ctp index 2bf9d9b..e411cb8 100644 --- a/views/layouts/ajax.ctp +++ b/views/layouts/ajax.ctp @@ -1,2 +1,10 @@ <?php /* SVN FILE: $Id: ajax.ctp 600 2008-08-07 17:55:23Z AD7six $ */ -echo $content_for_layout; ?> \ No newline at end of file +if($session->check('Message.auth')): + $session->flash('auth'); +endif; +if($session->check('Message.flash')): + $session->flash(); +endif; +echo $content_for_layout; +//echo $miJavascript->link(); +?> \ No newline at end of file diff --git a/views/layouts/default.ctp b/views/layouts/default.ctp index 7258871..3879d54 100644 --- a/views/layouts/default.ctp +++ b/views/layouts/default.ctp @@ -189,9 +189,9 @@ echo $html->meta('keywords', </div> </div> <?php -echo $miJavascript->link(array('jquery' => array()), false); +echo $miJavascript->link(array('jquery' => array('form')), false); echo $miJavascript->link('jquery-ui', false); -echo $miJavascript->link('scripts', false); +echo $miJavascript->link(array('scripts', 'popup'), false); echo $miJavascript->link(); if(isProduction()): ?> diff --git a/views/nodes/add.ctp b/views/nodes/add.ctp index 352f5eb..4a33059 100755 --- a/views/nodes/add.ctp +++ b/views/nodes/add.ctp @@ -1,4 +1,4 @@ -<div class="nodes add"> +<div class="nodes add ajaxFormContainer"> <?php $contents = ''; if (isset($this->data['Revision']['content'])) { diff --git a/views/nodes/admin_merge.ctp b/views/nodes/admin_merge.ctp index b7051d4..5670d9d 100644 --- a/views/nodes/admin_merge.ctp +++ b/views/nodes/admin_merge.ctp @@ -2,7 +2,7 @@ if (isset($preview)) : ?> <div id='preview' class="nodes view"> - <h2><?php echo htmlspecialchars($preview['title']) ?> </h2> + <h2><?php echo h($preview['title']) ?> </h2> <div class="body"><?php if (isset($highlight)) { echo $highlight->auto($preview['content']); diff --git a/views/nodes/compare.ctp b/views/nodes/compare.ctp index 0fe25ca..76e342f 100644 --- a/views/nodes/compare.ctp +++ b/views/nodes/compare.ctp @@ -2,12 +2,16 @@ <?php $compare = array(); foreach ($data as $key => $row) { - echo '<h2>{' . up($row['Revision']['lang']) . '} - ' . $row['Node']['sequence'] . ' ' . htmlspecialchars($row['Revision']['title']) . '</h2>'; - //echo $html->clean($currentNode['Revision']['content']); - echo '<div class="summary">' . $row['Revision']['content'] . '</div>'; + if (empty($this->params['isAjax'])) { + echo '<h2>{' . up($row['Revision']['lang']) . '} - ' . $row['Node']['sequence'] . ' ' . h($row['Revision']['title']) . '</h2>'; + //echo $html->clean($currentNode['Revision']['content']); + echo '<div class="summary">' . $row['Revision']['content'] . '</div>'; + } $compare[$key] = '<title>' . $row['Revision']['title'] . "</title>\r\n" . $row['Revision']['content']; } -echo '<h2>' . __('Differences', true) . '</h2>'; -echo $diff->compare(htmlspecialchars($compare['compare']), htmlspecialchars($compare['original'])); +if (empty($this->params['isAjax'])) { + echo '<h2>' . __('Differences', true) . '</h2>'; +} +echo $diff->compare(h($compare['compare']), h($compare['original'])); ?> </div> \ No newline at end of file diff --git a/views/nodes/edit.ctp b/views/nodes/edit.ctp index da1bacb..c3a1429 100755 --- a/views/nodes/edit.ctp +++ b/views/nodes/edit.ctp @@ -1,4 +1,4 @@ -<div class="nodes form"> +<div class="nodes form ajaxFormContainer"> <?php $contents = ''; if (isset($this->data['Revision']['content'])) { diff --git a/views/nodes/english_todo.ctp b/views/nodes/english_todo.ctp index 844fd16..c037394 100644 --- a/views/nodes/english_todo.ctp +++ b/views/nodes/english_todo.ctp @@ -7,7 +7,7 @@ foreach ($data as $id => $row) { $sequence = $row['Node']['sequence']; $sequence = $sequence?$sequence:'#'; echo "<h2 id=\"{$row['Revision']['slug']}-{$row['Node']['id']}\">" . - $html->link($sequence, array('action' => 'view', $row['Node']['id'], $row['Revision']['slug'])) . ' ' . htmlspecialchars($row['Revision']['title']) . "</h2>"; + $html->link($sequence, array('action' => 'view', $row['Node']['id'], $row['Revision']['slug'])) . ' ' . h($row['Revision']['title']) . "</h2>"; echo '<div class="options">'; echo $this->element('node_options', array('data' => $row)); diff --git a/views/nodes/history.ctp b/views/nodes/history.ctp index 3d42ef4..00972aa 100644 --- a/views/nodes/history.ctp +++ b/views/nodes/history.ctp @@ -1,5 +1,8 @@ -<h1>History</h1> -<?php if ($this->params['lang'] != $defaultLang) { +<?php if (empty($this->params['isAjax'])) : ?> + <h1><?php __('History') ?></h1> +<?php +endif; +if ($this->params['lang'] != $defaultLang) { $url = $this->passedArgs; $url[] = 1; echo '<p>' . $html->link(__('See English edits too', true), $url) . '</p>'; diff --git a/views/nodes/todo.ctp b/views/nodes/todo.ctp index c77894b..ab31f86 100644 --- a/views/nodes/todo.ctp +++ b/views/nodes/todo.ctp @@ -19,7 +19,7 @@ foreach ($data as $id => $row) { $sequence = $row['Node']['sequence']; $sequence = $sequence?$sequence:'#'; echo "<h2 id=\"{$row['Revision']['slug']}-{$row['Node']['id']}\">" . - $html->link($sequence, array('action' => 'view', $row['Node']['id'], $row['Revision']['slug'])) . ' ' . htmlspecialchars($row['Revision']['title']) . "</h2>"; + $html->link($sequence, array('action' => 'view', $row['Node']['id'], $row['Revision']['slug'])) . ' ' . h($row['Revision']['title']) . "</h2>"; echo '<div class="options">'; echo $this->element('node_options', array('data' => $row)); diff --git a/views/nodes/view_all.ctp b/views/nodes/view_all.ctp index e15fbf2..d82a39f 100644 --- a/views/nodes/view_all.ctp +++ b/views/nodes/view_all.ctp @@ -17,7 +17,7 @@ if ($this->params['isAjax']) { $this->set('currentNode', $currentNode['Node']); $attributes = ''; echo "<h2 id= \"{$currentNode['Revision']['slug']}-{$currentNode['Node']['id']}\">" . - $currentNode['Node']['sequence'] . ' ' . htmlspecialchars($currentNode['Revision']['title']) . "</h2>"; + $currentNode['Node']['sequence'] . ' ' . h($currentNode['Revision']['title']) . "</h2>"; echo '<div class="options">'; echo $this->element('node_options', array('data' => $currentNode)); @@ -36,10 +36,6 @@ if ($this->params['isAjax']) { false ); } - echo '<div class="comments" id="comments-' . $currentNode['Node']['id'] . '">'; - echo '<div class="comment">'; - echo $html->link(__('See comments for this section', true), array('controller' => 'comments', 'action' => 'index', $currentNode['Node']['id'])); - echo '</div></div>'; array_shift ($data); foreach ($data as $id => $row) { @@ -49,7 +45,7 @@ if ($this->params['isAjax']) { $sequence = $row['Node']['sequence']; $sequence = $sequence?$sequence:'#'; echo "<h$level id=\"{$row['Revision']['slug']}-{$row['Node']['id']}\">" . - $html->link($sequence, '#' . $row['Revision']['slug'] . '-' . $row['Node']['id']) . ' ' . htmlspecialchars($row['Revision']['title']) . "</h$level>"; + $html->link($sequence, '#' . $row['Revision']['slug'] . '-' . $row['Node']['id']) . ' ' . h($row['Revision']['title']) . "</h$level>"; echo '<div class="options">'; echo $this->element('node_options', array('data' => $row)); diff --git a/views/revisions/admin_index.ctp b/views/revisions/admin_index.ctp index dd8b28c..ca60b02 100755 --- a/views/revisions/admin_index.ctp +++ b/views/revisions/admin_index.ctp @@ -41,8 +41,8 @@ foreach ($data as $row) { $row['Node']?$html->link($row['Node']['sequence'], am($pass, array('page' => 1, 'node_id' => $row['Revision']['node_id']))):'', $html->link($row['Revision']['title'], array('action' => 'view', $row['Revision']['id'])), $html->link($row['Revision']['lang'], am($pass, array('page' => 1, 'lang:' . $row['Revision']['lang']))), - $User?$html->link($User['username'], am($pass, array('page' => 1, 'user_id' => $row['Revision']['user_id']))):'', - $User?'<a href="mailto:' . $User['email'] . '">' . $User['email'] . '</a>':'', + $row['User']?$html->link($row['User']['username'], am($pass, array('page' => 1, 'user_id' => $row['Revision']['user_id']))):'', + $row['User']?'<a href="mailto:' . $row['User']['email'] . '">' . $row['User']['email'] . '</a>':'', $html->link($row['Revision']['status'], am($pass, array('page' => 1, 'status' => $row['Revision']['status']))), $html->link($row['Revision']['created'], am($pass, array('page' => 1, 'created' => $row['Revision']['created']))), ); diff --git a/views/revisions/admin_pending.ctp b/views/revisions/admin_pending.ctp index bc7053c..4d29c22 100755 --- a/views/revisions/admin_pending.ctp +++ b/views/revisions/admin_pending.ctp @@ -4,7 +4,7 @@ foreach ($counts as $lang => $count) { $menu->add(array( 'section' => 'Options' , - 'title' => sprintf(__n('%1$s pending %2$s submission', '%3$s pending %4$s submissions', $count, true), $count, up($lang)), + 'title' => sprintf(__n('%1$s pending %2$s submission', '%1$s pending %2$s submissions', $count, true), $count, up($lang)), 'url' => array('language' => $lang), 'under' => 'Pending' )); diff --git a/views/revisions/admin_view.ctp b/views/revisions/admin_view.ctp index cc79023..66bb7b2 100755 --- a/views/revisions/admin_view.ctp +++ b/views/revisions/admin_view.ctp @@ -13,7 +13,7 @@ echo '<p>' . up($data['Revision']['lang']) . array('admin' => false, 'controller' => 'nodes', 'action' => 'view', 'lang' => $data['Revision']['lang'], $data['Node']['id'])) . ' ' . $time->timeAgoInWords($data['Revision']['created']) . '</p>'; if(!empty($data['Revision']['reason'])) { - echo '<p>Reason: ' . htmlspecialchars($data['Revision']['reason']) . '</p>'; + echo '<p>Reason: ' . h($data['Revision']['reason']) . '</p>'; } echo "<h2>" . $data['Revision']['title'] . "</h2>"; echo $this->element('node_options', array ( @@ -26,15 +26,16 @@ echo $this->element('node_options', array ( ) )); echo $data['Revision']['content']; -$revisionContent = htmlspecialchars_decode('<title>' . $data['Revision']['title'] . "</title>\r\n" . $data['Revision']['content']); +$revisionContent = htmlspecialchars_decode('<title>' . $data['Revision']['title'] . "</title>\r\n" . $data['Revision']['content'], 'UTF-8'); if ($data['Revision']['node_id'] && isset($current) && $data['Revision']['id'] != $current['Revision']['id']) { echo '<hr />'; echo '<h2>{Current} ' . $current['Revision']['title'] . '</h2>'; echo $current['Revision']['content']; - $currentContent = htmlspecialchars_decode('<title>' . $current['Revision']['title'] . "</title>\r\n" . $current['Revision']['content']); + $currentContent = htmlspecialchars_decode('<title>' . $current['Revision']['title'] . "</title>\r\n" . $current['Revision']['content'], + 'UTF-8'); echo '<hr />'; echo '<h2>Changes</h2>'; - echo $diff->compare(htmlspecialchars($currentContent),htmlspecialchars($revisionContent)); + echo $diff->compare(h($currentContent),h($revisionContent)); } echo $this->element('node_navigation'); diff --git a/views/revisions/view.ctp b/views/revisions/view.ctp index 3c001d5..c2c2d56 100644 --- a/views/revisions/view.ctp +++ b/views/revisions/view.ctp @@ -2,14 +2,14 @@ <?php $compare = array(); foreach ($data as $key => $row) { - echo '<h2>{' . up($row['Revision']['id']) . '} - ' . $row['Node']['sequence'] . ' ' . htmlspecialchars($row['Revision']['title']) . '</h2>'; + echo '<h2>{' . up($row['Revision']['id']) . '} - ' . $row['Node']['sequence'] . ' ' . h($row['Revision']['title']) . '</h2>'; //echo $html->clean($currentNode['Revision']['content']); echo '<div class="summary">' . $row['Revision']['content'] . '</div>'; $compare[] = '<title>' . $row['Revision']['title'] . "</title>\r\n" . $row['Revision']['content']; } if (count($compare) > 1) { echo '<h2>' . __('Differences', true) . '</h2>'; - echo $diff->compare(htmlspecialchars($compare[0]), htmlspecialchars($compare[1])); + echo $diff->compare(h($compare[0]), h($compare[1])); } ?> </div> \ No newline at end of file diff --git a/webroot/js/jquery.form.js b/webroot/js/jquery.form.js new file mode 100644 index 0000000..042df15 --- /dev/null +++ b/webroot/js/jquery.form.js @@ -0,0 +1,632 @@ +/* + * jQuery Form Plugin + * version: 2.18 (06-JAN-2009) + * @requires jQuery v1.2.2 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id: jquery.form.js 6061 2009-01-07 01:43:18Z malsup $ + */ +;(function($) { + +/* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are intended to be exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').bind('submit', function() { + $(this).ajaxSubmit({ + target: '#output' + }); + return false; // <-- important! + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + +/** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ +$.fn.ajaxSubmit = function(options) { + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + if (typeof options == 'function') + options = { success: options }; + + options = $.extend({ + url: this.attr('action') || window.location.toString(), + type: this.attr('method') || 'GET' + }, options || {}); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var a = this.formToArray(options.semantic); + if (options.data) { + options.extraData = options.data; + for (var n in options.data) { + if(options.data[n] instanceof Array) { + for (var k in options.data[n]) + a.push( { name: n, value: options.data[n][k] } ) + } + else + a.push( { name: n, value: options.data[n] } ); + } + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else + options.data = q; // data is the query string for 'post' + + var $form = this, callbacks = []; + if (options.resetForm) callbacks.push(function() { $form.resetForm(); }); + if (options.clearForm) callbacks.push(function() { $form.clearForm(); }); + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + $(options.target).html(data).each(oldSuccess, arguments); + }); + } + else if (options.success) + callbacks.push(options.success); + + options.success = function(data, status) { + for (var i=0, max=callbacks.length; i < max; i++) + callbacks[i].apply(options, [data, status, $form]); + }; + + // are there files to upload? + var files = $('input:file', this).fieldValue(); + var found = false; + for (var j=0; j < files.length; j++) + if (files[j]) + found = true; + + // options.iframe allows user to force iframe mode + if (options.iframe || found) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if ($.browser.safari && options.closeKeepAlive) + $.get(options.closeKeepAlive, fileUpload); + else + fileUpload(); + } + else + $.ajax(options); + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + + if ($(':input[name=submit]', form).length) { + alert('Error: Form elements must not be named "submit".'); + return; + } + + var opts = $.extend({}, $.ajaxSettings, options); + var s = jQuery.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts); + + var id = 'jqFormIO' + (new Date().getTime()); + var $io = $('<iframe id="' + id + '" name="' + id + '" />'); + var io = $io[0]; + + if ($.browser.msie || $.browser.opera) + io.src = 'javascript:false;document.write("");'; + $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' }); + + var xhr = { // mock object + aborted: 0, + responseText: null, + responseXML: null, + status: 0, + statusText: 'n/a', + getAllResponseHeaders: function() {}, + getResponseHeader: function() {}, + setRequestHeader: function() {}, + abort: function() { + this.aborted = 1; + $io.attr('src','about:blank'); // abort op in progress + } + }; + + var g = opts.global; + // trigger ajax global events so that activity/block indicators work like normal + if (g && ! $.active++) $.event.trigger("ajaxStart"); + if (g) $.event.trigger("ajaxSend", [xhr, opts]); + + if (s.beforeSend && s.beforeSend(xhr, s) === false) { + s.global && jQuery.active--; + return; + } + if (xhr.aborted) + return; + + var cbInvoked = 0; + var timedOut = 0; + + // add submitting element to data if we know it + var sub = form.clk; + if (sub) { + var n = sub.name; + if (n && !sub.disabled) { + options.extraData = options.extraData || {}; + options.extraData[n] = sub.value; + if (sub.type == "image") { + options.extraData[name+'.x'] = form.clk_x; + options.extraData[name+'.y'] = form.clk_y; + } + } + } + + // take a breath so that pending repaints get some cpu time before the upload starts + setTimeout(function() { + // make sure form attrs are set + var t = $form.attr('target'), a = $form.attr('action'); + $form.attr({ + target: id, + method: 'POST', + action: opts.url + }); + + // ie borks in some cases when setting encoding + if (! options.skipEncodingOverride) { + $form.attr({ + encoding: 'multipart/form-data', + enctype: 'multipart/form-data' + }); + } + + // support timout + if (opts.timeout) + setTimeout(function() { timedOut = true; cb(); }, opts.timeout); + + // add "extra" data to form if provided in options + var extraInputs = []; + try { + if (options.extraData) + for (var n in options.extraData) + extraInputs.push( + $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />') + .appendTo(form)[0]); + + // add iframe to doc and submit the form + $io.appendTo('body'); + io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false); + form.submit(); + } + finally { + // reset attrs and remove "extra" input elements + $form.attr('action', a); + t ? $form.attr('target', t) : $form.removeAttr('target'); + $(extraInputs).remove(); + } + }, 10); + + function cb() { + if (cbInvoked++) return; + + io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false); + + var operaHack = 0; + var ok = true; + try { + if (timedOut) throw 'timeout'; + // extract the server response from the iframe + var data, doc; + + doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document; + + if (doc.body == null && !operaHack && $.browser.opera) { + // In Opera 9.2.x the iframe DOM is not always traversable when + // the onload callback fires so we give Opera 100ms to right itself + operaHack = 1; + cbInvoked--; + setTimeout(cb, 100); + return; + } + + xhr.responseText = doc.body ? doc.body.innerHTML : null; + xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc; + xhr.getResponseHeader = function(header){ + var headers = {'content-type': opts.dataType}; + return headers[header]; + }; + + if (opts.dataType == 'json' || opts.dataType == 'script') { + var ta = doc.getElementsByTagName('textarea')[0]; + xhr.responseText = ta ? ta.value : xhr.responseText; + } + else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) { + xhr.responseXML = toXml(xhr.responseText); + } + data = $.httpData(xhr, opts.dataType); + } + catch(e){ + ok = false; + $.handleError(opts, xhr, 'error', e); + } + + // ordering of these callbacks/triggers is odd, but that's how $.ajax does it + if (ok) { + opts.success(data, 'success'); + if (g) $.event.trigger("ajaxSuccess", [xhr, opts]); + } + if (g) $.event.trigger("ajaxComplete", [xhr, opts]); + if (g && ! --$.active) $.event.trigger("ajaxStop"); + if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error'); + + // clean up + setTimeout(function() { + $io.remove(); + xhr.responseXML = null; + }, 100); + }; + + function toXml(s, doc) { + if (window.ActiveXObject) { + doc = new ActiveXObject('Microsoft.XMLDOM'); + doc.async = 'false'; + doc.loadXML(s); + } + else + doc = (new DOMParser()).parseFromString(s, 'text/xml'); + return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null; + }; + }; +}; + +/** + * ajaxForm() provides a mechanism for fully automating form submission. + * + * The advantages of using this method instead of ajaxSubmit() are: + * + * 1: This method will include coordinates for <input type="image" /> elements (if the element + * is used to submit the form). + * 2. This method will include the submit element's name/value data (for the element that was + * used to submit the form). + * 3. This method binds the submit() method to the form for you. + * + * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely + * passes the options argument along after properly binding events for submit elements and + * the form itself. + */ +$.fn.ajaxForm = function(options) { + return this.ajaxFormUnbind().bind('submit.form-plugin',function() { + $(this).ajaxSubmit(options); + return false; + }).each(function() { + // store options in hash + $(":submit,input:image", this).bind('click.form-plugin',function(e) { + var form = this.form; + form.clk = this; + if (this.type == 'image') { + if (e.offsetX != undefined) { + form.clk_x = e.offsetX; + form.clk_y = e.offsetY; + } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin + var offset = $(this).offset(); + form.clk_x = e.pageX - offset.left; + form.clk_y = e.pageY - offset.top; + } else { + form.clk_x = e.pageX - this.offsetLeft; + form.clk_y = e.pageY - this.offsetTop; + } + } + // clear form vars + setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10); + }); + }); +}; + +// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm +$.fn.ajaxFormUnbind = function() { + this.unbind('submit.form-plugin'); + return this.each(function() { + $(":submit,input:image", this).unbind('click.form-plugin'); + }); + +}; + +/** + * formToArray() gathers form element data into an array of objects that can + * be passed to any of the following ajax functions: $.get, $.post, or load. + * Each object in the array has both a 'name' and 'value' property. An example of + * an array for a simple login form might be: + * + * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] + * + * It is this array that is passed to pre-submit callback functions provided to the + * ajaxSubmit() and ajaxForm() methods. + */ +$.fn.formToArray = function(semantic) { + var a = []; + if (this.length == 0) return a; + + var form = this[0]; + var els = semantic ? form.getElementsByTagName('*') : form.elements; + if (!els) return a; + for(var i=0, max=els.length; i < max; i++) { + var el = els[i]; + var n = el.name; + if (!n) continue; + + if (semantic && form.clk && el.type == "image") { + // handle image inputs on the fly when semantic == true + if(!el.disabled && form.clk == el) + a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); + continue; + } + + var v = $.fieldValue(el, true); + if (v && v.constructor == Array) { + for(var j=0, jmax=v.length; j < jmax; j++) + a.push({name: n, value: v[j]}); + } + else if (v !== null && typeof v != 'undefined') + a.push({name: n, value: v}); + } + + if (!semantic && form.clk) { + // input type=='image' are not found in elements array! handle them here + var inputs = form.getElementsByTagName("input"); + for(var i=0, max=inputs.length; i < max; i++) { + var input = inputs[i]; + var n = input.name; + if(n && !input.disabled && input.type == "image" && form.clk == input) + a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); + } + } + return a; +}; + +/** + * Serializes form data into a 'submittable' string. This method will return a string + * in the format: name1=value1&amp;name2=value2 + */ +$.fn.formSerialize = function(semantic) { + //hand off to jQuery.param for proper encoding + return $.param(this.formToArray(semantic)); +}; + +/** + * Serializes all field elements in the jQuery object into a query string. + * This method will return a string in the format: name1=value1&amp;name2=value2 + */ +$.fn.fieldSerialize = function(successful) { + var a = []; + this.each(function() { + var n = this.name; + if (!n) return; + var v = $.fieldValue(this, successful); + if (v && v.constructor == Array) { + for (var i=0,max=v.length; i < max; i++) + a.push({name: n, value: v[i]}); + } + else if (v !== null && typeof v != 'undefined') + a.push({name: this.name, value: v}); + }); + //hand off to jQuery.param for proper encoding + return $.param(a); +}; + +/** + * Returns the value(s) of the element in the matched set. For example, consider the following form: + * + * <form><fieldset> + * <input name="A" type="text" /> + * <input name="A" type="text" /> + * <input name="B" type="checkbox" value="B1" /> + * <input name="B" type="checkbox" value="B2"/> + * <input name="C" type="radio" value="C1" /> + * <input name="C" type="radio" value="C2" /> + * </fieldset></form> + * + * var v = $(':text').fieldValue(); + * // if no values are entered into the text inputs + * v == ['',''] + * // if values entered into the text inputs are 'foo' and 'bar' + * v == ['foo','bar'] + * + * var v = $(':checkbox').fieldValue(); + * // if neither checkbox is checked + * v === undefined + * // if both checkboxes are checked + * v == ['B1', 'B2'] + * + * var v = $(':radio').fieldValue(); + * // if neither radio is checked + * v === undefined + * // if first radio is checked + * v == ['C1'] + * + * The successful argument controls whether or not the field element must be 'successful' + * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls). + * The default value of the successful argument is true. If this value is false the value(s) + * for each element is returned. + * + * Note: This method *always* returns an array. If no valid value can be determined the + * array will be empty, otherwise it will contain one or more values. + */ +$.fn.fieldValue = function(successful) { + for (var val=[], i=0, max=this.length; i < max; i++) { + var el = this[i]; + var v = $.fieldValue(el, successful); + if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) + continue; + v.constructor == Array ? $.merge(val, v) : val.push(v); + } + return val; +}; + +/** + * Returns the value of the field element. + */ +$.fieldValue = function(el, successful) { + var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); + if (typeof successful == 'undefined') successful = true; + + if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || + (t == 'checkbox' || t == 'radio') && !el.checked || + (t == 'submit' || t == 'image') && el.form && el.form.clk != el || + tag == 'select' && el.selectedIndex == -1)) + return null; + + if (tag == 'select') { + var index = el.selectedIndex; + if (index < 0) return null; + var a = [], ops = el.options; + var one = (t == 'select-one'); + var max = (one ? index+1 : ops.length); + for(var i=(one ? index : 0); i < max; i++) { + var op = ops[i]; + if (op.selected) { + // extra pain for IE... + var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value; + if (one) return v; + a.push(v); + } + } + return a; + } + return el.value; +}; + +/** + * Clears the form data. Takes the following actions on the form's input fields: + * - input text fields will have their 'value' property set to the empty string + * - select elements will have their 'selectedIndex' property set to -1 + * - checkbox and radio inputs will have their 'checked' property set to false + * - inputs of type submit, button, reset, and hidden will *not* be effected + * - button elements will *not* be effected + */ +$.fn.clearForm = function() { + return this.each(function() { + $('input,select,textarea', this).clearFields(); + }); +}; + +/** + * Clears the selected form elements. + */ +$.fn.clearFields = $.fn.clearInputs = function() { + return this.each(function() { + var t = this.type, tag = this.tagName.toLowerCase(); + if (t == 'text' || t == 'password' || tag == 'textarea') + this.value = ''; + else if (t == 'checkbox' || t == 'radio') + this.checked = false; + else if (tag == 'select') + this.selectedIndex = -1; + }); +}; + +/** + * Resets the form data. Causes all form elements to be reset to their original value. + */ +$.fn.resetForm = function() { + return this.each(function() { + // guard against an input with the name of 'reset' + // note that IE reports the reset function as an 'object' + if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) + this.reset(); + }); +}; + +/** + * Enables or disables any matching elements. + */ +$.fn.enable = function(b) { + if (b == undefined) b = true; + return this.each(function() { + this.disabled = !b + }); +}; + +/** + * Checks/unchecks any matching checkboxes or radio buttons and + * selects/deselects and matching option elements. + */ +$.fn.selected = function(select) { + if (select == undefined) select = true; + return this.each(function() { + var t = this.type; + if (t == 'checkbox' || t == 'radio') + this.checked = select; + else if (this.tagName.toLowerCase() == 'option') { + var $sel = $(this).parent('select'); + if (select && $sel[0] && $sel[0].type == 'select-one') { + // deselect all other options + $sel.find('option').selected(false); + } + this.selected = select; + } + }); +}; + +// helper fn for console logging +// set $.fn.ajaxSubmit.debug to true to enable debug logging +function log() { + if ($.fn.ajaxSubmit.debug && window.console && window.console.log) + window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,'')); +}; + +})(jQuery); diff --git a/webroot/js/popup.js b/webroot/js/popup.js new file mode 100644 index 0000000..2b7f06f --- /dev/null +++ b/webroot/js/popup.js @@ -0,0 +1,126 @@ +/* SVN FILE: $Id: popup.js 874 2009-03-24 13:30:58Z ad7six $ */ +/** + * popup.js for making dialogs + * + * Requires jquery-ui and the forms plugin + * + * Copyright (c) 2008, Andy Dawson + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright (c) 2008, Andy Dawson + * @link www.ad7six.com + * @package base + * @subpackage base.vendors.js + * @since v 1.0 + * @version $Revision: 874 $ + * @modifiedby $LastChangedBy: ad7six $ + * @lastmodified $Date: 2009-03-24 14:30:58 +0100 (Tue, 24 Mar 2009) $ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +$(function() { + /** + * Attach jquery-ui.dialog to links that match the conditions + * adds.ajax suffix to ensure full page view caching doesn't get confused + * and then calls containLinks so that links and forms stay within the dialog + */ + $('.dialogs a, .popup a, a.popup, a.dialog, a.popout, a.confirm') + .click(function(){ + var _this = $(this); + var title = _this.attr('title'); + if (!title) { + title = _this.text(); + } + var el = $('<div class="dialog"><p></p></div>'); + el + .attr('title', title) + .appendTo('body') + .load(_this.attr('href') + '.ajax', function(){ + contain(el); + el.dialog({ + autoOpen: false, + width: 500, + height: 400, + modal: el.hasClass('confirm|modal')?true:false + }).dialog('open'); + }); + return false; + }); + /** + * contain method + * + * contain both links and forms + * + * @return void + * @access public + */ + function contain (base) { + containLinks(base); + containForms(base); + } + /** + * containLinks method + * + * For the passed base find any links within it and ajax load into the same container - + * unless it's a popout, in which case load in its own dialog instead + */ + function containLinks (base) { + var base = $(base); + $('a', base).click(function() { + var _this = $(this); + var title = _this.attr('title'); + if (!title) { + title = _this.text(); + } + + if (_this.hasClass('popout')) { + _this + .click(function(){ + $('<div class="dialog" style="display;none">Loading...</div>') + .attr('title', title) + .appendTo('body') + .load(_this.attr('href') + '.ajax', function(){ + contain(_this); + }).dialog({ + autoOpen: false, + width: 500, + height: 400, + }); + return false; + }); + } else { + base.load(_this.attr('href') + '.ajax', function() { + contain(base); + }); + } + return false; + }); + } + /** + * containForms method + * + * For the passed base find any forms and ajax load into the same container + */ + function containForms (base) { + var base = $(base); + $('form', base).bind('submit', function(){ + $(this).ajaxSubmit({ + target: base, + success: function(r) { + base.html(r); + containForms(base); + }, + }); + return false; + }); + } + /** + * For any div with the class ajaxFormContainer - load the form into the same container + * This prevents losing any open (none-modal) dialogs in the process, hence it's here + */ + $('div.ajaxFormContainer').each(function(){ + containForms(this); + }); +}); \ No newline at end of file