2

I’m updating a site for my brother who teaches training courses. He has a registration form on the site that collects name, age, address, etc. That information is sent to him through cfmail with a copy sent to the registrant. The registrants then mails in a check via snail-mail to complete the registration. (My brother does NOT want to use an online payment method.)

Included in the form is the course name, location and fee. He asked if it was possible to implement some sort of “Promo Code” to offer discounts to select users. I’ve added PromoCode and PromoCode_Fee columns in SQL and am able to make it all work throughout the process.

My problem is on the user end. If the user mistypes the PromoCode in the form, the app will obviously not register the discount, send the registration emails out with the standard fee, and store the registration info in the DB. The only way for the user to fix the PromoCode would be to re-register, which would re-send the emails and add a new registration to the DB.

What I’d like to do is verify that the user entered a valid PromoCode in the input field PRIOR to submitting the form by comparing what they typed to the PromoCode stored in the DB. If the PromoCode doesn’t match, add “Promo Code is invalid” under the input field.

I do this as a hobby, am self-taught and am not sure if it’s even possible (or good idea.) I imagine it’s not possible to do with ColdFusion and would most likely need some sort of JS or jQuery - both of which I’m pretty illiterate in.

I’ve been searching for hours to see if anyone had any similar questions, but have come up short. Any help or pointing me in the right direction would be greatly appreciated.

Here's the code I'm putting together:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> 
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="/scripts/jquery.validate.js"></script>

<script>
    $(document).ready(function() {
        var validator = $("#signupform").validate({
            rules: {
                firstname: "required",
                lastname: "required",
                username: {
                    required: true,
                    minlength: 2,
                    remote: "/components/promocodecomponent.cfc?method=validateUserName"
                }
            }
        });
    });
</script>


<div class="row justify-content-center">
    <div class="col-10">
        <form id="signupform" autocomplete="off" method="get" action="">
            <table>
                <tr>
                    <td class="label">
                        <label id="lfirstname" for="firstname">First Name</label>
                    </td>
                    <td class="field">
                        <input id="firstname" name="firstname" type="text" value="" maxlength="100">
                    </td>
                    <td class="status"></td>
                </tr>
                <tr>
                    <td class="label">
                        <label id="llastname" for="lastname">Last Name</label>
                    </td>
                    <td class="field">
                        <input id="lastname" name="lastname" type="text" value="" maxlength="100">
                    </td>
                    <td class="status"></td>
                </tr>
                <tr>
                    <td class="label">
                        <label id="lusername" for="username">Username</label>
                    </td>
                    <td class="field">
                        <input id="username" name="username" type="text" value="" maxlength="50">
                    </td>
                    <td class="status"></td>
                </tr>
                <tr>
                    <td class="field" colspan="2">
                        <input id="signupsubmit" name="signup" type="submit" value="Signup">
                    </td>
                </tr>
            </table>
        </form>
    </div>
</div>

Here's the component code:

component {
    remote boolean function validateUserName(string username) returnFormat="json"{
        
        if (arguments.username == "john") {
            return true;
        }

        return "Username already in use";
    }

}
MJRoz
  • 73
  • 7

2 Answers2

2

Usually, you'd need to post some code that you've tried and isn't working. But you've outlined what you want and are just not sure where to start.

You can test just the value of the discount code before allowing the whole form to be submitted. You don't say how you're doing client-side form validation. I'd suggest using jQuery Validate to handle that, it's very easy to implement.

https://jqueryvalidation.org/documentation/

Go to the demo "The Remember The Milk sign-up form". This form checks the username field via Ajax before the rest of the form can be submitted.

var validator = $("#signupform").validate({
    rules: {
        firstname: "required",
        lastname: "required",
        username: {
            required: true,
            minlength: 2,
            remote: "users.action"
        }
    }
});

If not using this framework, then just make an Ajax request when change is triggered on the discount code field and make sure there's a positive response from that before you allow the form to be submitted.

Also, you need to do server-side validation of the discount code when someone submits the form. If they've entered a discount code that is invalid, then don't allow the form to be processed until they enter a valid code or they clear the value from that field.

Adrian J. Moreno
  • 14,350
  • 1
  • 37
  • 44
  • I'm not sure I understand or if your suggestion would work in this case. I have a form validation that was written by someone. I can set the PromoCode field to "required" and force the user to enter something, but that's not what I'm trying to do. If the user doesn’t have the PromoCode, they should be able to leave it blank and still submit the form. What I’m hoping to do is if they type in a code, a message will display stating whether the code is valid or not PRIOR to submitting the form. Similar to your suggested “The Remember The Milk sign-up form when creating the Username “johndoe.” – MJRoz Feb 09 '22 at 02:50
  • @MJRoz - The Milk form just chose to make the field required, but that's optional. The key part he's highlighting is [the `remote` attribute](https://jqueryvalidation.org/remote-method/). It's what triggers the ajax call to validate the username. In your case you'd point it to a cfc method that validates the promo code, something like `promocode: { ... , remote: "/path/YourComponent.cfc?method=validatePromoCode" }` – SOS Feb 09 '22 at 03:49
  • I've looked through the code and duplicated it on the server but can't get it to work even in it's present form. In short, I'm completely lost. Thanks much for the help! – MJRoz Feb 09 '22 at 18:38
  • Ultimately you don't need the whole form, but what do you mean by can't get it to work - error message? If yes, don't keep us in suspense, what's the error? :-P – SOS Feb 09 '22 at 20:01
  • Fair enough. The form loads fine... I added the scripts on the server: but have no idea how to set up the "remote" or have a Component.cfc. I'm in over my head I think. ;( – MJRoz Feb 09 '22 at 21:31
  • Here are the errors on Chrome Dev Tools: Uncaught SyntaxError: Unexpected token '<' jquery.mockjax.js:66 Uncaught ReferenceError: jQuery is not defined at jquery.mockjax.js:66:167 jquery.validate.js:4 Uncaught ReferenceError: jQuery is not defined at jquery.validate.js:4:151 at jquery.validate.js:4:159 RegistrationTest.cfm?firstname=&lastname=&username=&password=&password_confirm=&email=&signup=Signup:13 Uncaught ReferenceError: $ is not defined at RegistrationTest.cfm:13:2 – MJRoz Feb 09 '22 at 21:33
  • 1
    (Edit) The `remote` value is a url. That url would be the path to a cfcomponent you create on the server. The component should contain a method that validates the promo code. That method should simply return true/false. It's not difficult, but if you're unfamiliar with cfcomponents, you have some reading to do first, see https://modern-cfml.ortusbooks.com/cfml-language/components. Also, it's best to append that much code/error messages to the question. Use the code button {} to format. – SOS Feb 10 '22 at 02:38
  • `jquery.mockjax.js:66 Uncaught ReferenceError: jQuery is not defined at` Sounds like it's not finding jquery for some reason. Could be a bad path, but hard to say without being able to see your code. – SOS Feb 10 '22 at 08:12
  • Hmmm... I appreciate your help. I'll read up on cfcomponent and see if I can figure it out. Got to wrap up some front end stuff first but will definitely get beck to you. – MJRoz Feb 10 '22 at 16:50
1

I do this as a hobby, am self-taught ... I imagine ... would most likely need some sort of JS or jQuery - both of which I’m pretty illiterate in.

The plugin is easy to use but there may be a slight learning curve depending on your jQuery and javascript skills. Bigger tasks are easier to tackle if you you break them into smaller ones and solve those one at a time. Start with the code snippet @AdrianJMoreno posted and delve into the documentation to understand what the code is doing and how:

  • validate() - a function that initializes the plugin for a specific form
  • rules - options that tells the plugin which form fields should be validated and how
  • remote - remote url to be called via ajax to validate a field's value. The remote endpoint should either return true/false or true/"some custom error message";

1. Build Sample Form

Once you have a sense of how the plugin works, move on to building a scaled down version of the Milk demo form using the code snippet.

JQuery and Validate libraries

   <!-- modify js paths as needed -->
   <script src="scripts/jquery-3.1.1.js"></script>
   <script src="scripts/jquery.validate.min.js"></script>

Javascript initialization (so plugin validates the form)

    <script>
    $(document).ready(function() {
        var validator = $("#signupform").validate({
            rules: {
                firstname: "required",
                lastname: "required",
                username: {
                    required: true,
                    minlength: 2,
                    remote: "users.action"
                }
            }
        });
    });
    </script>

  

HTML form (only the fields in code snippet)

    <form id="signupform" autocomplete="off" method="get" action="">
        <table>
            <tr>
                <td class="label">
                    <label id="lfirstname" for="firstname">First Name</label>
                </td>
                <td class="field">
                    <input id="firstname" name="firstname" type="text" value="" maxlength="100">
                </td>
                <td class="status"></td>
            </tr>
            <tr>
                <td class="label">
                    <label id="llastname" for="lastname">Last Name</label>
                </td>
                <td class="field">
                    <input id="lastname" name="lastname" type="text" value="" maxlength="100">
                </td>
                <td class="status"></td>
            </tr>
            <tr>
                <td class="label">
                    <label id="lusername" for="username">Username</label>
                </td>
                <td class="field">
                    <input id="username" name="username" type="text" value="" maxlength="50">
                </td>
                <td class="status"></td>
            </tr>
            <tr>
                <td class="label">
                    <label id="lsignupsubmit" for="signupsubmit">Signup</label>
                </td>
                <td class="field" colspan="2">
                    <input id="signupsubmit" name="signup" type="submit" value="Signup">
                </td>
            </tr>       
            </table>
    </form>

2. Test (Sample Form)

Test out the form in a browser. Since all fields are required, leaving the fields blank and submitting should trigger error messages on submit

Form With Errors

3. Create Remote URL

The live demo form uses a mocking library to simulate a remote ajax call. The mock url "users.action" must be replaced with a real url on your server. That url will point to a new component (or "CFC") you create. The component should contain a remote accessible method that validates a given username.

Keep things simple for the initial test. Have the method return true if the input equals a test value like "John" and false for everything else. Ultimately you'll replace that with real business logic, like a database lookup, but one step at a time.

YourComponentName.cfc

component {
    remote boolean function validateUserName(string username) returnFormat="json"{
        
        if (arguments.username == "john") {
            return true;
        }

        return false;
    }

}

4. Test (Remote URL)

To test a remote method in a browser, specify the path, name of the method to invoke, and any parameters that method expects. Verify the component returns the expected results. The result should be true for username=John and false for any other value.

Example url:

https://localhost/path/YourComponentName.cfc?method=validateUsername&username=john

Valid: UserName=John:

Test Remote URL with Valid Value

InValid: UserName=Bob:

Test Remote URL with Invalid Value

5. Fix Remote URL

With the component working, update the javascript code to point to the new cfc. Omit the username parameter because the plugin will pass it to the ajax call automatically.

For example, if the component location on disk is:

c:\path\webroot\path\YourComponentName.cfc 

Use the url:

/path/YourComponentName.cfc

JS Snippet:

...,
username: {
   ...,
   remote: "/path/YourComponentName.cfc?method=validateUserName"
}

6. Test Form (.. are you sensing a theme?)

Test the final form again with both a valid and invalid usernames to confirm it works as expected.

  • Invalid usernames "Mike" or "Bob" should trigger an error
    Invalid Username
  • Valid username "John" should not trigger an error Valid UserName

Next Steps ...

Continue to expand the working example and learn more about the plugin features by adding some customization. The next steps are left as an exercise for the reader ...

  • Replace hard coded test logic in the cfc with a database lookup
  • Replace default error message "Please fix this field" with a more user friendly message
  • Change the appearance of valid and invalid fields using CSS
SOS
  • 6,430
  • 2
  • 11
  • 29
  • I'm trying to work through the process with your help (Thank You!) I added the code to the original question. (Couldn't seem to find another way.) Step 4. Test (Remote URL) is giving me a 403 ERROR so I just added the link to the component to - remote: "/components/promocodecomponent.cfc" but it's not working. No errors in Chrome dev tools... validation for required fields works fine. – MJRoz Feb 15 '22 at 03:18
  • Yeah, you have to get Step 4 working first. That step lets you see what the plugin will see when it calls the component. If it doesn't work for you - it won't work for the plugin either. Looks like you went "off script" and added `` tags (-; The sample code already defines the component using cfscript, i.e. `component { .... }` so the extra tags cause an error. Remove them and try again. If you're still getting an error, post the full path to the component `c:\path\to\yourComponent.cfc` and the full url you're testing in your browser. – SOS Feb 15 '22 at 06:47
  • So... I'm getting an error on Chrome Dev Tools when adding the full path to the component whether I add d:\path... or https://example.com... _Access to XMLHttpRequest at 'https://example.com/components/promocodecomponent.cfc?method=validateUserName&username=bob' from origin 'https://www.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource._ – MJRoz Feb 16 '22 at 20:31
  • So to confirm, did you get Step 4 working correctly? To answer your question, you don't use the local path on disk in your javascript code, it must be a *url*. Typically a relative path, relative to your web root. For example, say your component location on disk is `d:\path\webroot\components\YourComponentName.cfc`, you'd use something like `/components/YourComponentName.cfc` where the first `/` means the webroot of your server. – SOS Feb 16 '22 at 23:27
  • When I use the relative path `/components/promocodecomponent.cfc` I do NOT get any errors... however, nothing happens. – MJRoz Feb 16 '22 at 23:34
  • I've updated the code in the question to exactly what I'm doing. – MJRoz Feb 16 '22 at 23:38
  • First, did Step 4 work or not? i.e. Can you access the component from a browser using `http://yourserver/components/promocodecomponent.cfc?method=validateUsername&username=john`? – SOS Feb 16 '22 at 23:45
  • Yes. I get "True" Apologies... this is all Greek to me. – MJRoz Feb 16 '22 at 23:56
  • And you refreshed/cleared cache and are still getting that error using `remote: /components/promocodecomponent.cfc?method=validateUserName`? Because it typically occurs when doing an ajax request to a different domain than you're currently on https://stackoverflow.com/a/70565195/8895292 – SOS Feb 17 '22 at 01:15
  • No. No errors. It just seems like the message **"John is already in use"** is not displaying. Everything else may be working, but without the message, I can't tell. – MJRoz Feb 17 '22 at 01:23
  • Ohh... I think you misunderstood the test. It's supposed to return an error for any username EXCEPT "John". i.e. *...The (function) result should be `true` for username=John and `false` for any other value...*. Meaning "John" is available. Any other username is NOT, and will trigger an error. – SOS Feb 17 '22 at 01:37
  • So how do I achieve my goal? Although we're testing with _"username"_, I ultimately want to check if the **PromoCode** matches what I have in the DB BEFORE submitting the form. e.g. If the user types in the wrong PromoCode and it doesn't match, I want a message to display "Invalid Promo Code." If the user types in the correct code, the message would say something like "Success." – MJRoz Feb 17 '22 at 01:54
  • (Edit) I understand, but one thing at a time. For now you're just trying to verify that *unavailable* usernames (or BAD promo codes) trigger an error message, and available usernames (or GOOD promo codes) **do not**. In this context "John" is an example of a good promo code and "Bob" or "Mike" is a bad promo code. You can customize how successful values are displayed in CSS later. Make sense? – SOS Feb 17 '22 at 02:27
  • So right now, if I go to `http://yourserver/components/promocodecomponent.cfc?method=validateUsername&username=john` I get the result "TRUE." If I change the name to Bob and go to `http://yourserver/components/promocodecomponent.cfc?method=validateUsername&username=bob' I get the expected 500 ERROR. Once I get the results, I can do something like: `SuccessInvalid Code`. How do I get the results? – MJRoz Feb 17 '22 at 02:32
  • *I get the expected 500 ERROR ...* No, you should NOT be getting a 500 error. You should see the word `false`. Are you using the *exact* component example I posted? – SOS Feb 17 '22 at 03:13
  • Yes; exactly as you posted. component { remote boolean function validateUserName(string username) returnFormat="json"{ if (arguments.username == "john") { return true; } return "Username already in use"; } } – MJRoz Feb 17 '22 at 03:38
  • My bad. I pasted the wrong line. The last should return `false`, not "Username already in use". – SOS Feb 17 '22 at 03:43
  • Got `false` now. – MJRoz Feb 17 '22 at 03:52
  • Now try Step 6. See images above for expected results – SOS Feb 17 '22 at 03:53
  • Got `Please fix this field.` when using anything other than _john._ – MJRoz Feb 17 '22 at 03:57
  • Good. The minimal example is working correctly. Time to move on to "Next steps" (1) customize error message and element css (2) replace hard coded logic with db query). If you run into issues, best open a new thread – SOS Feb 17 '22 at 04:16