octobercms / october

Self-hosted CMS platform based on the Laravel PHP Framework.
https://octobercms.com/
Other
11.01k stars 2.21k forks source link

'TypeError: jQuery is undefined' after partial update #3991

Closed tyamz closed 5 years ago

tyamz commented 5 years ago

[UPDATE]

I have been able to pinpoint the issue down to a particular partial update. It is only when I update the partial with 'blogPosts/postList', but other partial updates do not cause the jQuery TypeError. I'm not sure what exactly is causing it. I've added more code to this post as well as the paths to the files to help understand what's going on.

PLEASE NOTE: There is a lot of code below. I did not just post random code hoping you could find the issue. I've selected these code blocks purposely because I feel that they may be relevant to finding out whether this is a bug or just a mistake on my part. I feel that I've exhausted my personal search efforts which is why I'm asking here anyway.

I have an OctoberCMS component that has an AJAX Handler for onSelectTag, but when the #posts HTML element is updated (rendered) with the blogPosts/postList partial, the following error is thrown:

TypeError: jQuery is undefined

/plugins/example/blog/components/filter/tagList.htm:

Below is the taglist.htm file, this is what is calling the AJAX handler. This component partial is actually one of the partial updates, but this one does not break.

{% for tag in tags %}
<button type="button" class="list-group-item" data-request="onSelectTag" data-request-data="tagId: {{ tag.id }}">{{ tag.name }}</button>
{% endfor %}

/themes/demo/pages/blog.htm

Below is blog.htm, this is where the #posts element is and this is where the Filter component is referenced on the page.

title = "Blog"
url = "/blog/:page?"
layout = "blog-layout"
description = "Latest updates"
meta_title = "Example Blog"
meta_description = "Blog page"
is_hidden = 0

[blogFilter]
mainPage = 1

[Example\Blog\Components\Posts blogPosts]
pageNumber = "{{ :page }}"
postsPerPage = 10
noPostsMessage = "No posts found"
sortOrder = "published_at desc"
categoryPage = "blog-posts-category"
postPage = "blog-post"
hideFeatured = 0
relatedPosts = 0
==
<div class="container">
    <div class="col-md-4">
        {% component 'blogFilter' %}
    </div>
    <div id="posts">
        {% component 'blogPosts' %}
    </div>
</div>

/themes/demo/layouts/blog-layout.htm:

Below is blog-layout.htm, this is the layout for the aforementioned blog.htm.

description = "Blog Layout"
==
<!DOCTYPE html>
<html>
    <head>
        <title>{{ this.page.title }} ~ Example Blog</title>
        <meta name="description" content="{{ this.page.meta_description }}">
        <meta name="title" content="{{ this.page.meta_title }}">
        <meta name="author" content="The Example Corporation">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta name="generator" content="The Example Corporation">
        <!-- To Add Links to Stylesheets, go to Partials >> site >> styles.htm -->
        {% partial 'site/styles' %}
        <link href="{{ 'assets/css/custom/blog.css'|theme }}" rel="stylesheet">
        <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
        <link rel="stylesheet" href="https://use.typekit.net/oww0xpm.css">
        {% styles %}
        <meta charset="utf-8">
        <meta property="og:url"           content="{{ this.page.id | page }}" />
        <meta property="og:type"          content="article" />
        <meta property="og:title"         content="{{ post.title }}" />
        <meta property="og:description"   content="{{ post.excerpt }}" />
        <meta property="og:image"         content="{{ post.featured_images[0].path }}" />
        <meta property="fb:app_id"        content="2436169626399425" />
    </head>
    <body>
        <div id="fb-root"></div>
        <script>(function(d, s, id) {
          var js, fjs = d.getElementsByTagName(s)[0];
          if (d.getElementById(id)) return;
          js = d.createElement(s); js.id = id;
          js.src = 'https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v3.2&appId=2436169626399425&autoLogAppEvents=1';
          fjs.parentNode.insertBefore(js, fjs);
        }(document, 'script', 'facebook-jssdk'));</script>

        <!-- Header -->
        <header id="layout-header">
            {% partial 'site/header' %}
        </header>

        <!-- Content -->
        <section id="layout-content" style="display: none">
            {% page %}
        </section>

        <!-- Footer -->
        <footer id="layout-footer" style="display: none">
            {% partial 'site/footer' %}
        </footer>

        <!-- Scripts -->
        <script src="{{ 'assets/vendor/jquery-3.3.1.min.js'|theme }}"></script>
        <script src="{{ 'assets/vendor/popper.min.js'|theme }}"></script>
        <script src="{{ 'assets/vendor/bootstrap.min.js'|theme }}"></script>
        <script src="{{ 'assets/javascript/app.js'|theme }}"></script>
        <script src="{{ 'assets/javascript/home.js'|theme }}"></script>
        <script src="{{ 'assets/javascript/blog.js'|theme }}"></script>
        {% framework extras %}
        {% scripts %}

    </body>
</html>

onSelectTag() AJAX Handler:

This is an excerpt from /plugins/example/blog/components/Filter.php, this is the onSelectTag() AJAX handler that actually calls the Partial Update (see comment in code below).

public function onSelectTag()
{
    $this->page['posts'] = Post::all();

    // below is where the partial updates / renders
    return [
        '#posts' => $this->renderPartial('blogPosts/postList.htm')
    ];
}

/themes/demo/partials/blogPosts/postList.htm:

Below is blogPosts/postList.htm, this is the actual partial that is loaded into the page in #posts.

[viewBag]
==
{% if posts is empty %}
{% set posts = __SELF__.posts %}
{% endif %}
{% if not hideFeatured is defined %}
  {% set hideFeatured = __SELF__.property('hideFeatured') %}
{% endif %}
{% set pageSlug = post.slug %}
{% if not hideFeatured %}
<div class="row" id="highlight">
  <div class="col-lg-8">
    <div class="card" id="featured">
      <a href="{{ posts.first.url }}">
        {% if posts.first.featured_images.count %}
          <img src="{{ posts.first.featured_images[0].path }}" alt="{{ posts.first.title }}" class="card-img-top">
        {% endif %}
        {% if posts.first.categories.count %}
            <p class="card-header">
            {% for category in posts.first.categories %}
                <a href="{{ category.url }}" class="badge badge-secondary">{{ category.name }}</a>
            {% endfor %}
            </p>
        {% endif %}
        <div class="card-body bg-success text-white">
            <h5 class="card-title"><a href="{{ posts.first.url }}" class="text-white">{{ posts.first.title | upper }}</a> <small class="float-right">{{ posts.first.published_at|date('F d, Y') }}</small></h5>
            <hr class="tagline"/>
            <p class="card-text">{{ posts.first.summary|striptags|length > 150 ? posts.first.summary|striptags|slice(0, 150) ~ '...' : posts.first.summary|striptags}}</p>
        </div>
        <div class="card-footer text-muted">
          <span><i class="fas fa-tags" aria-hidden="true"></i> Tags: </span>
          {% if posts.first.tags.count %}
              {% for tag in posts.first.tags %}
                  <span class="badge badge-primary">{{ tag.name }}</span>
              {% endfor %}
          {% else %}
              <span class="badge badge-danger">No Tags</span>
          {% endif %}
        </div>
      </a>
    </div>
  </div>
    <div class="col-lg-4">
        <div class="embed-responsive embed-responsive-16by9">
            {% partial 'youtube' %}
        </div>
        <br />
        <div class="card">
            <h4 class="card-header">SUBSCRIBE TO OUR NEWSLETTER</h4>
            <div class="card-body">
                {% partial 'mail-chimp' %}
            </div>
        </div>
    </div>
</div>
{% endif %}
<br />
<div class="card-columns">
{% for post in posts %}
    {% if loop.first and not hideFeatured %}
    <!-- First Post is Featured -->
    {% elseif post.slug != pageSlug %}
    <a href="{{ url('/blog/post/' ~ post.slug) }}">
        <div class="card">
            {% if post.featured_images.count %}
            <img src="{{ post.featured_images[0].path }}" class="card-img-top" alt="{{ post.title }}">
            {% endif %}
            <div class="card-body">
                <h4 class="card-title text-center"><a href="{{ post.url }}">{{ post.title | upper }}</a></h4>
                <p class="card-subtitle text-muted text-center">{{ post.published_at|date('F d, Y') }}</p>
                {% if post.categories.count %}
                    {% for category in post.categories %}
                    <a href="{{ url('/blog/category/' ~ category.slug) }}" class="badge badge-success">{{ category.name }}</a>
                    {% endfor %}
                {% endif %}
                <hr class="tagline" />
                <div class="card-text">{{ post.summary|striptags|length > 50 ? post.summary|striptags|slice(0, 50) ~ '...' : post.summary|striptags}}</div>
            </div>
            <div class="card-footer text-muted">
                <span><i class="fas fa-tags" aria-hidden="true"></i> Tags: </span>
                {% if post.tags.count %}
                    {% for tag in post.tags %}
                    <span class="badge badge-primary">{{ tag.name }}</span>
                    {% endfor %}
                {% else %}
                <span class="badge badge-danger">No Tags</span>
                {% endif %}
            </div>
        </div>
    </a>
    {% endif %}
{% else %}
<h3 class="no-data">{{ noPostsMessage }}</h3>
{% endfor %}
</div>

<div class="row">
{% if posts.lastPage > 1 %}
    <ul class="pagination">
        {% if posts.currentPage > 1 %}
            <li><a href="{{ this.page.baseFileName|page({ (pageParam): (posts.currentPage-1) }) }}">&larr; Prev</a></li>
        {% endif %}

        {% for page in 1..posts.lastPage %}
            <li class="{{ posts.currentPage == page ? 'active' : null }}">
                <a href="{{ this.page.baseFileName|page({ (pageParam): page }) }}">{{ page }}</a>
            </li>
        {% endfor %}

        {% if posts.lastPage > posts.currentPage %}
            <li><a href="{{ this.page.baseFileName|page({ (pageParam): (posts.currentPage+1) }) }}">Next &rarr;</a></li>
        {% endif %}
    </ul>
{% endif %}
</div>

/plugins/example/blog/component/posts/default.htm

It should be noted, that the default partial for the blogPosts Component is not /themes/demo/partials/blogPosts/postList.htm, instead it is /plugins/example/blog/components/posts/default.htm, the code is almost exactly identical. If necessary, I will post it, but I think there's enough code in this post.

I don't think the reference or load order is the issue because I've tried moving the jQuery reference and the {% framework extras %} reference and the {% scripts %} reference to many different parts of the layout, and nothing changed either way.

My theory:

I think that when the partial updates, it's messing up the jQuery Framework call or something. It's odd because jQuery functions still work like:

$('#tagFilter').on('input', function() {
    console.log("TEST");
});

That still works, but functions defined by the OctoberCMS AJAX framework like $.request(...) do not work. I'm not sure why this is happening though.

w20k commented 5 years ago

@tyamz, your scripts, and jQuery should be on the default layout, not on the page. Could you show the default layout?

tyamz commented 5 years ago

@tyamz, your scripts, and jQuery should be on the default layout, not on the page. Could you show the default layout?

I meant to say layout, not page. I've made some changes to the post and posted the layout code. Please take a look.

w20k commented 5 years ago

@tyamz, will take a look 😉

One more thing, @tyamz, you're using OctoberCMS without any plugins?

tyamz commented 5 years ago

@w20k Fortunately, the OctoberCMS plugins page copies over markdown-friendly.

Plugins

October Demo Provides features used by the provided demonstration theme. 1.0.1 Alexey Bobkov, Samuel Georges
Location Location based features, such as Country and State. 1.0.8 Alexey Bobkov, Samuel Georges
News and Newsletter Simple news and newsletter plugin. 1.10.9 Gergő Szabó
User Front-end user management. 1.4.6 Alexey Bobkov, Samuel Georges
Builder Provides visual tools for building October plugins. 1.0.23 Alexey Bobkov, Samuel Georges
Pages Pages & menus features. 1.2.20 Alexey Bobkov, Samuel Georges
Backend Plus New useful features and widgets for back-end. 1.6.8 Gergő Szabó
Wysiwyg Editors Inject your favorite Wysiwyg Editor to CMS and other Code Editor 1.2.9 Anand Patel
User Downloads User Download Manager 1.0.9 Thomas Yamakaitis (for The Compuflex Corporation - © 2017 The Compuflex Inc. All rights reserved.
Contact Contact Us Form 1.0.8 Thomas Yamakaitis (for The Compuflex Corporation - © 2017 The Compuflex Inc. All rights reserved.
FAQs Frequently Asked Questions Plugin 1.0.5 Thomas Yamakaitis
Company Extends the RainLab User Plugin to assign users to companies 1.0.14 Thomas Yamakaitis
Blog A robust blogging platform. 1.2.19 Alexey Bobkov, Samuel Georges
Mail Statistics and logging of emails. 1.3.0 Matiss Janis Aboltins
Logger This is a simple plugin that logs the identity of machines requesting specific pages. 1.0.2 Thomas Yamakaitis
Administrator This plugin will extend the back end user model to enhance and add features for the back end user experience. 1.0.2 Thomas Yamakaitis
MailChimp Provides MailChimp integration services. 1.0.4 Alexey Bobkov, Samuel Georges
Sitemap Generate a sitemap.xml file for your website. 1.0.8 Alexey Bobkov, Samuel Georges
Compuflex Blog Extends the RainLab Blog Plugin to add additional functionality 1.0.12 Thomas Yamakaitis
Renatio Logout Manage user session. 3.0.1 Renatio
LinkedIn This plugin will allow users to log in with their LinkedIn account and post comments to the blog. 1.0.5 Thomas Yamakaitis
Gallery Dynamic Photo Gallery plugin that creates dynamic photo displays. 1.0.6 Thomas Yamakaitis
w20k commented 5 years ago

@tyamz, could you also take a look what response you get from ajax call? In dev tools -> network tab? And also post what is inside the blog.js if possible.

tyamz commented 5 years ago

@tyamz, could you also take a look what response you get from ajax call? In dev tools -> network tab? And also post what is inside the blog.js if possible.

@w20k I'm confused, it just randomly started working again. I'll close this for now.