Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>I hope I can propose a better way to solve the problem mentioned by <strong>Soph</strong>. I know this is not JavaScript-related solution this question is addicted to but as I can see this one is the most right way to go.</p> <p>I faced the same requirement - to refresh the caption on page refresh but preserve this one when user's input is invalid on other fields because repeating a new verification code on each validation's fail is annoying. So this task looks like to not refresh the caption on <code>POST</code> request and refresh it otherwise (<code>GET</code> request). A solution proposed by <strong>Soph</strong> by himself can't achieve this - as one could know <code>POST</code> requests are sent to server and their data cannot be analyzed by client-side JavaScript.</p> <p>Therefore I decided to look at Yii's captcha implementation. It consists of two classes located in <code>system.web.widgets.captcha</code> package. The former is <code>CCaptcha</code> extended from <code>CWidget</code> and that we use in our view to add a captcha to web form in the manner like this:</p> <pre><code>&lt;?php $this-&gt;widget("CCaptcha", array( 'buttonLabel' =&gt; "Generate another code", 'showRefreshButton' =&gt; false, 'clickableImage' =&gt; true )); ?&gt; </code></pre> <p>The latter is <code>CCaptchaAction</code> that does the most significant work to provide a captcha by generating a verification code and creating an image. Taking into account that Yii is designed to be object-oriented we can create a couple of our own classes <code>Captcha</code> and <code>CaptchaAction</code> extended from <code>CCaptcha</code> and <code>CCaptchaAction</code> relatively and place them in <code>components</code> subdirectory of our web application's directory.</p> <p>My implementation of both these classes is below.</p> <p><code>run()</code> method of <code>CCaptchaAction</code> is overriden and is quite similar to original one excepting that there is one additional <code>if</code>-branch executing when <code>render_refreshed</code> GET-parameter is set and where new verification code is being generated on the fly and corresponding new image is being rendered and returned as action's result.</p> <p>A public <code>refresh</code> variable has been introduced in <code>Captcha</code> class and defaults to <code>false</code> meaning widget's behavior is similar to <code>CCaptcha</code>'s one. Overriding <code>renderImage</code> method we alter a code responsible for preparing widget's output HTML code. To be more precisive it's where a link to captcha action is prepared used as <code>src</code> attribute of an <code>img</code> tag. In the case of <code>refresh</code> member is set to <code>true</code> an additional <code>render_refreshed</code> parameter is appended to this link.</p> <p>Here is <code>CaptchaAction.php</code>:</p> <pre><code>&lt;?php Yii::import("system.web.widgets.captcha.CCaptchaAction"); class CaptchaAction extends CCaptchaAction { const RENDER_REFRESHED_GET_VAR = "render_refreshed"; public function run() { if (isset($_GET[self::REFRESH_GET_VAR])) // AJAX request for regenerating code { $code = $this-&gt;getVerifyCode(true); echo CJSON::encode(array( 'hash1' =&gt; $this-&gt;generateValidationHash($code), 'hash2' =&gt; $this-&gt;generateValidationHash(strtolower($code)), // we add a random 'v' parameter so that FireFox can refresh the image // when src attribute of image tag is changed 'url'=&gt; $this-&gt;getController()-&gt;createUrl($this-&gt;getId(), array( 'v' =&gt; uniqid() )), )); } else if (isset($_GET[self::RENDER_REFRESHED_GET_VAR])) { $this-&gt;renderImage($this-&gt;getVerifyCode(true)); } else $this-&gt;renderImage($this-&gt;getVerifyCode()); Yii::app()-&gt;end(); } } </code></pre> <p>And this is <code>Captcha.php</code>:</p> <pre><code>&lt;?php Yii::import("web.system.widgets.CCaptcha"); class Captcha extends CCaptcha { public $refresh = false; protected function renderImage() { if (!isset($this-&gt;imageOptions['id'])) $this-&gt;imageOptions['id'] = $this-&gt;getId(); if ($this-&gt;refresh) { $url = $this-&gt;getController()-&gt;createUrl($this-&gt;captchaAction, array( 'v' =&gt; uniqid(), CaptchaAction::RENDER_REFRESHED_GET_VAR =&gt; 1 )); } else { $url = $this-&gt;getController()-&gt;createUrl($this-&gt;captchaAction, array( 'v' =&gt; uniqid() )); } $alt = isset($this-&gt;imageOptions['alt']) ? $this-&gt;imageOptions['alt'] : ''; echo CHtml::image($url, $alt, $this-&gt;imageOptions); } } </code></pre> <p>So the usage is quite simple. In action preparing model's data for site's page do the following:</p> <pre><code>... // Creating order model's instance $model = new MyModel(); $refreshCaptcha = true; if (isset($_POST['MyModel'])) { $refreshCaptcha = false; ... } ... $this-&gt;render("myView", array( 'model' =&gt; $model, 'refreshCaptcha' =&gt; $refreshCaptcha )); </code></pre> <p>After that modify captcha's widget call in <code>myView</code> template of this page's action:</p> <pre><code>&lt;?php $this-&gt;widget("Captcha", array( 'buttonLabel' =&gt; "Generate another code", 'showRefreshButton' =&gt; false, 'clickableImage' =&gt; true, 'refresh' =&gt; $refreshCaptcha )); </code></pre> <p>and don't forget to modify <code>actions</code> method of page's controller by replacing <code>CCaptchaAction</code> class by <code>CaptchaAction</code>:</p> <pre><code>public function actions() { return array( 'captcha'=&gt;array( 'class'=&gt;'CaptchaAction', 'backColor'=&gt;0xFFFFFF ) ); } </code></pre> <p>Seems to me that it works as expected. Hope this will help somebody.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload