--- name: Wheels View Generator description: Generate Wheels view templates with proper query handling, form helpers, and association display. Use when creating or modifying views, forms, layouts, or partials. Prevents common view errors like query/array confusion and incorrect form helper usage. Handles index views, show views, form views, and layouts with proper CFML syntax. --- # Wheels View Generator ## When to Use This Skill Activate automatically when: - User requests to create a view (e.g., "create an index view for posts") - User wants to create forms - User needs to display associated data - User is creating layouts or partials - User mentions: view, template, form, layout, partial, display, list, show ## Critical Anti-Patterns to Prevent ### ❌ ANTI-PATTERN 1: Query/Array Confusion **Wheels associations return QUERIES, not arrays!** **WRONG:** ```cfm ❌ ❌ ``` **CORRECT:** ```cfm ✅ ✅ ``` ### ❌ ANTI-PATTERN 2: Association Access Inside Query Loops **WRONG:** ```cfm

#posts.comments().recordCount# comments

❌ Fails!
``` **CORRECT:** ```cfm

#postComments.recordCount# comments

✅ Works!
``` ### ❌ ANTI-PATTERN 3: Non-Existent Form Helpers **Wheels doesn't have these helpers:** ```cfm #emailField(...)# ❌ Doesn't exist #passwordField(...)# ❌ Doesn't exist #numberField(...)# ❌ Doesn't exist ``` **Use textField() with type attribute:** ```cfm #textField(objectName="user", property="email", type="email")# ✅ #textField(objectName="user", property="password", type="password")# ✅ #textField(objectName="user", property="age", type="number")# ✅ ``` ### ❌ ANTI-PATTERN 4: HTML in linkTo() Text **linkTo() HTML-encodes the text parameter by default for security:** ```cfm ❌ WRONG - HTML will be escaped and displayed as text: #linkTo(text="", controller="home", action="index")# ``` **Use manual anchor tag with urlFor() for HTML content:** ```cfm ✅ CORRECT - HTML renders properly: ``` ### ❌ ANTI-PATTERN 5: Assuming allErrors() Returns Array **allErrors(propertyName) can return string OR array:** ```cfm ❌ WRONG - Assumes always array:
  • #error#
  • ``` **✅ BEST PRACTICE - Inline Ternary (Task 4 Pattern):** ```cfm

    #isArray(emailErrors) ? emailErrors[1] : emailErrors#

    ### ❌ ANTI-PATTERN 6: Wrong startFormTag() Pattern for Update Forms **CRITICAL: Using action="update" without route causes forms to submit as GET:** ```cfm ❌ WRONG - Submits as GET, doesn't reach update action: #startFormTag(action="update", method="patch")# ❌ ALSO WRONG - Missing key parameter: #startFormTag(action="update", key=user.id, method="patch")# ``` **✅ CORRECT - Use route with key for RESTful updates:** ```cfm #startFormTag(action="create", method="post")# #startFormTag(route="user", key=user.id, method="patch")# #startFormTag(action="create", method="post")# #startFormTag(route="user", key=user.id, method="patch")# ``` **Why This Matters:** - `action="update"` tries to route to /controller/update (wrong) - `route="user"` with `key` routes to /users/[id] (correct RESTful pattern) - Without proper route+key, form submits as GET to wrong endpoint - PATCH method requires correct route to work properly ``` **✅ ALTERNATIVE - Explicit Type Check (for multiple errors):** ```cfm
  • #error#
  • #propertyErrors#
  • ``` **📝 Production Note (Task 4):** The inline ternary pattern is cleaner for form field errors where you only need to display the first error message. ## 🚨 Production-Tested Best Practices ### 1. Association Access in Query Loops (CRITICAL) **❌ WRONG - Associations Don't Work on Query Columns:** ```cfm #tweets.user()# ❌ FAILS - tweets is a query, not an object #tweets.likesCount()# ❌ FAILS - no such method on query ``` **✅ CORRECT - Load Object First:** ```cfm ✅ Works!

    By: #tweetUser.username#

    Likes: #tweetObj.likesCount#

    ``` **✅ BETTER - Preload with include (Prevents N+1):** ```cfm tweets = model("Tweet").findAll(include="user", order="createdAt DESC");

    By: #tweets.username#

    Tweet: #tweets.content#

    ``` ### 2. Checking if Query Has Records ```cfm ... ❌ Error! ``` ### 3. Counter Access Patterns **Direct column access (if eager loaded):** ```cfm tweets = model("Tweet").findAll(select="id,content,likesCount");

    #tweets.likesCount# likes

    ✅ Direct access
    ``` **Association count (if not loaded):** ```cfm ✅ Count association

    #likeCount# likes

    ``` ### 4. Boolean Checks in Views ```cfm // Redundant // Fails if bio doesn't exist ``` ### 5. Date Formatting ```cfm #dateFormat(tweet.createdAt, "mmm dd, yyyy")# #timeFormat(tweet.createdAt, "h:mm tt")# #tweet.timeAgo()# // "5m", "2h", "3d" ``` ### 6. Loop Query vs Loop Array ```cfm #tweets.content# ❌ Error! ``` ## Index View Template (List View) ```cfm #contentFor(pageTitle="Resources")#

    Resources

    #linkTo(text="New Resource", action="new", class="btn btn-primary")#

    #linkTo(text=resources.title, action="show", key=resources.id)#

    #left(resources.description, 200)#...

    #resourceAssoc.recordCount# items #dateFormat(resources.createdAt, "mmm dd, yyyy")#
    #linkTo(text="View", action="show", key=resources.id, class="btn btn-sm")# #linkTo(text="Edit", action="edit", key=resources.id, class="btn btn-sm")#
    #paginationLinks(prependToLink="page=")#

    No resources found.

    #linkTo(text="Create First Resource", action="new", class="btn btn-primary")#
    ``` ## Show View Template (Detail View) ```cfm #contentFor(pageTitle=resource.title)#

    #resource.title#

    #linkTo(text="Edit", action="edit", key=resource.id, class="btn")# #linkTo( text="Delete", action="delete", key=resource.id, method="delete", confirm="Are you sure?", class="btn btn-danger" )# #linkTo(text="Back to List", action="index", class="btn")#
    #resource.description#

    Associated Items (#associations.recordCount#)

    • #associations.name# #dateFormat(associations.createdAt, "mmm dd")#

    No associated items.

    ``` ## Form View Template (New/Edit) ```cfm #contentFor(pageTitle=resource.isNew() ? "New Resource" : "Edit Resource")#

    #resource.isNew() ? "Create" : "Edit"# Resource

    Please correct the following errors:

    • #errorMessage#
    #startFormTag(action="create", method="post")# #startFormTag(route="resource", key=resource.id, method="patch")#
    #textField(objectName="resource", property="title", label=false, class="form-control")# #isArray(titleErrors) ? titleErrors[1] : titleErrors#
    #textArea(objectName="resource", property="description", label=false, rows=6, class="form-control")# #isArray(descErrors) ? descErrors[1] : descErrors#
    #textField(objectName="resource", property="email", type="email", label=false, class="form-control")# #isArray(emailErrors) ? emailErrors[1] : emailErrors#
    #textField(objectName="resource", property="price", type="number", step="0.01", label=false, class="form-control")#
    #dateSelect(objectName="resource", property="publishedDate", label=false, class="form-control")#
    #select( objectName="resource", property="status", options="draft,published,archived", label=false, class="form-control" )#
    #select( objectName="resource", property="categoryId", options=model("Category").findAll(), valueField="id", textField="name", includeBlank="-- Select Category --", label=false, class="form-control" )#
    #submitTag(value=resource.isNew() ? "Create Resource" : "Update Resource", class="btn btn-primary")# #linkTo(text="Cancel", action="index", class="btn")#
    #endFormTag()#
    ``` ## Layout Template ```cfm #contentFor("pageTitle")# - My App #styleSheetLinkTag("application")# #csrfMetaTags()#
    #flash("success")#
    #flash("error")#
    #flash("notice")#
    #includeContent()#

    © #year(now())# My App. All rights reserved.

    #javaScriptIncludeTag("application")# ``` ## Partial Template ```cfm

    #linkTo(text=resource.title, action="show", key=resource.id)#

    #resource.excerpt()#

    #dateFormat(resource.createdAt, "mmm dd, yyyy")#
    ``` ## Form Helper Reference ### Text Inputs ```cfm #textField(objectName="user", property="name")# #textField(objectName="user", property="email", type="email")# #textField(objectName="user", property="password", type="password")# #textField(objectName="user", property="age", type="number")# #textField(objectName="user", property="website", type="url")# #textField( objectName="user", property="name", class="form-control", placeholder="Enter your name", maxlength=100, required=true )# ``` ### Textarea ```cfm #textArea(objectName="post", property="content", rows=10, cols=50)# ``` ### Select Dropdown ```cfm #select(objectName="user", property="role", options="user,admin,moderator")# #select( objectName="post", property="categoryId", options=model("Category").findAll(), valueField="id", textField="name", includeBlank="-- Select Category --" )# ``` ### Checkboxes and Radio Buttons ```cfm #checkBox(objectName="user", property="active")# #radioButton(objectName="user", property="gender", tagValue="male")# Male #radioButton(objectName="user", property="gender", tagValue="female")# Female ``` ### Date/Time Selects ```cfm #dateSelect(objectName="event", property="eventDate")# #timeSelect(objectName="event", property="eventTime")# #dateTimeSelect(objectName="event", property="eventDateTime")# ``` ## Link Helper Reference ```cfm #linkTo(text="View", action="show", key=resource.id)# #linkTo(text="Home", controller="home", action="index")# #linkTo(text="Delete", action="delete", key=resource.id, method="delete", confirm="Are you sure?")# #linkTo(text="Wheels Docs", href="https://wheels.dev", target="_blank")# #linkTo(text="Edit", action="edit", key=resource.id, class="btn btn-primary", data-turbo="false")# ``` ## Implementation Checklist When generating a view: - [ ] Use `` to declare expected variables - [ ] Use `` blocks for dynamic content - [ ] Use `.recordCount` for query counts (not ArrayLen) - [ ] Use `` for query iteration - [ ] Handle association access correctly in loops - [ ] Use textField() with type attribute (not emailField, etc.) - [ ] Display validation errors for each field - [ ] Include CSRF protection in forms (automatic with startFormTag) - [ ] Add flash message displays - [ ] Use contentFor() to set page titles - [ ] Provide empty state messages when no records ## Related Skills - **wheels-anti-pattern-detector**: Validates view code - **wheels-controller-generator**: Creates controllers that supply view data - **wheels-model-generator**: Creates models displayed in views --- **Generated by:** Wheels View Generator Skill v1.0 **Framework:** CFWheels 3.0+ **Last Updated:** 2025-10-20