Splunk Dev

Splunk 6 w/ Django -- Getting JQuery not defined javascript error

joshuamcqueen
Path Finder

Hey Splunk Gurus,

I am writing a custom Splunk 6 application using Django bindings. Basically, it's similar to the Music App tutorial.

Anyhow -- I'm loading in a third-party javascript plugin that uses jQuery. It's a jquery data grid called jqgrid

When I try to display a data grid on a template, I get a "Uncaught ReferenceError: jQuery is not defined"

Here is a screenshot: imgur.com/LpWCrTD

Now, my experience tells me jquery isn't loaded when this javascript gets called. I looked at other examples (and plugins) and they seems to works. So what's going on here? When is jquery loaded into page?

Here is my code:

{% extends "splunkdj:base_with_app_bar.html" %}

{% load splunkmvc %}

{% block title %}Edit Profiles{% endblock title %}
{% block css %}
    <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}{{app_name}}/custom.css" />
    <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}splunkjs/css/dashboard.css" />

    <link rel="stylesheet" type="text/css" media="screen" href="{{STATIC_URL}}{{app_name}}/ui-lightness/jquery-ui.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="{{STATIC_URL}}{{app_name}}/jqGrid/css/ui.jqgrid.css" /> 

{% endblock css %}

{% block content %}
    <div style="padding: 50px">
        <p>Hello World</p>
    </div>

    {% table id="table_searchresults" managerid="search_resulttable" %}
    {% chart id="chart_sourcetype" managerid="search_chart" type="pie" %}
{% endblock content%}

{% block managers %}
    {% searchmanager
        id="search_resulttable"
        search="index=_internal sourcetype=splunkd | head 3"
        cache=True
    %}
    {% searchmanager
        id="search_chart"
        search="index=_internal | head 1000 | stats count by sourcetype"
        cache=True
    %}
{% endblock managers %}

{% block js %}
<script src="{{STATIC_URL}}{{app_name}}/jqGrid/js/i18n/grid.locale-en.js" type="text/javascript"></script>
<script src="{{STATIC_URL}}{{app_name}}/jqGrid/js/jquery.jqGrid.src.js" type="text/javascript"></script>
{% endblock js %}
0 Karma
1 Solution

ineeman
Splunk Employee
Splunk Employee

Good question, and the answer is a bit subtle.

We load very few of our JS dependencies using script tags, mostly because it
quickly becomes problematic to work out all the dependencies, especially as we
don't know what components you're going to load on a page.

Given that, we use require.js (http://requirejs.org/) to do our dependency
management and loading. That's why you see all these instances in the examples
of doing things like:

require(['splunkjs/mvc/chartview'], function(ChartView) { ... });

However, this makes things a bit interesting in your case, where you want to
load something that depends on jQuery (jqgrid, in your case). The reason you're
getting the error is that when your <script> tags get executed, jQuery hasn't
loaded yet, because it hasn't finished being required. As such, when jqgrid tries
to reference the jQuery global variable, it can't.

So what's the solution? There are a couple of options:

  1. You can use require.js to load jqgrid. This would probably mean doing a
    require.config call in your page to define the paths (and possibly the shim
    definitions). This might look like this:

    require.config({
        paths: {
            "jqgrid.locale": "{{STATIC_URL}}{{app_name}}/jqGrid/js/i18n/grid.locale-en.js",
            "jqgrid": "{{STATIC_URL}}{{app_name}}/jqGrid/js/jquery.jqGrid.src.js"
        },
        shim: {
            "jqgrid.locale": {
                deps: ["jquery"]
            },
            "jqgrid": {
                deps: ["jquery", "grid.locale"],
                exports: "jQuery.fn.jqGrid"
            }
        }
    });
    
    require(["jqgrid"], function(jqgrid) {
        // ...
    });
    

    I don't know that the above code is 100% correct, but it's a rough estimation
    of what this might look like in a require-ified way.

    One interesting note is that you are going to be dependent on the JS Stack's
    version

  2. Another option might be to include your own version of jQuery, and then when
    the rest of your dependencies are loaded, you can call noConflict() on it. This
    might look like:

    <script src="{{STATIC_URL}}{{app_name}}/jquery.js" type="text/javascript"></script>
    <script src="{{STATIC_URL}}{{app_name}}/jqGrid/js/i18n/grid.locale-en.js" type="text/javascript"></script>
    <script src="{{STATIC_URL}}{{app_name}}/jqGrid/js/jquery.jqGrid.src.js" type="text/javascript"></script>
    <script>
        jQuery.noConflict();
    </script>
    

    That way, you know you can always rely on your version of jQuery, and also that
    only the jqgrid code will have access to that jQuery reference.

Hopefully the above options make sense. Let me know if I can expand or explain
something further. Also, we always love to hear what people are building, so
feel free to share 🙂

View solution in original post

joshuamcqueen
Path Finder

I actually solved this in a very hacky way. Basically I wait until splunk is ready, then I use a jquery getScript() to pull in the external scripts.

Now, if you look closely you'll notice they are nested. This is because there's AJAX taking place, and I need to wait for everything to come back before I can start using it. Nesting the functions() solves this.

Although it works, this is definitely not the best approach. Requirejs (like ineeman mentioned above) seems a lot cleaner.

<script>
require(["splunkjs/ready!"], function(mvc) {   

    //get the scripts using jquery
    $.getScript( "/dj/static/josh_app/jqGrid/js/i18n/grid.locale-en.js", function( data, textStatus, jqxhr ) {
    console.log( "grid.locale-en.js load was performed." );


        $.getScript( "/dj/static/josh_app/jqGrid/js/jquery.jqGrid.min.js", function( data, textStatus, jqxhr ) {
        console.log( "jquery.jqGrid.min.js load was performed." );

        console.log("about to access the grid"); 
        //the rest of this is all jqGrid stuff 
        jQuery("#list2").jqGrid({
            url:'http://172.16.127.128:8000/dj/en-us/josh_app/ajax-request',
            datatype: "json",
            colNames:['ID','User', 'Date', 'Profile Name','Low Warn','Low Error','High Warn','High Error'],
            colModel:[
                {name:'id',index:'id'},
                {name:'userid',index:'userid'},
                {name:'date',index:'date'},
                {name:'profile_name',index:'profile_name'},
                {name:'low_warn',index:'low_warn'},     
                {name:'low_error',index:'low_error'},   
                {name:'high_warn',index:'high_warn'},   
                {name:'high_error',index:'high_error'}                                                          
            ],
            rowNum:10,
            rowList:[10,25,50],
            pager: '#pager2',
            sortname: 'id',
            viewrecords: true,
            sortorder: "desc",
            caption:"Threshold Profiles"
        });

        jQuery("#list2").jqGrid('navGrid','#pager2',{edit:false,add:false,del:false});        

        });       
    }); 
});
</script>   

ineeman
Splunk Employee
Splunk Employee

This is not necessarily hacky. A different way to do the above is just to use nested require calls.

So it might look something like:

require(["jquery"], function() {
    require(["/path/to/grid.locale.js"], function() {
        require(["/path/to/jqgrid.js"], function() {
            // Use $.grid here
        });
    });
})

That's not a super uncommon require-approach for non-AMD scripts.

0 Karma

ineeman
Splunk Employee
Splunk Employee

Good question, and the answer is a bit subtle.

We load very few of our JS dependencies using script tags, mostly because it
quickly becomes problematic to work out all the dependencies, especially as we
don't know what components you're going to load on a page.

Given that, we use require.js (http://requirejs.org/) to do our dependency
management and loading. That's why you see all these instances in the examples
of doing things like:

require(['splunkjs/mvc/chartview'], function(ChartView) { ... });

However, this makes things a bit interesting in your case, where you want to
load something that depends on jQuery (jqgrid, in your case). The reason you're
getting the error is that when your <script> tags get executed, jQuery hasn't
loaded yet, because it hasn't finished being required. As such, when jqgrid tries
to reference the jQuery global variable, it can't.

So what's the solution? There are a couple of options:

  1. You can use require.js to load jqgrid. This would probably mean doing a
    require.config call in your page to define the paths (and possibly the shim
    definitions). This might look like this:

    require.config({
        paths: {
            "jqgrid.locale": "{{STATIC_URL}}{{app_name}}/jqGrid/js/i18n/grid.locale-en.js",
            "jqgrid": "{{STATIC_URL}}{{app_name}}/jqGrid/js/jquery.jqGrid.src.js"
        },
        shim: {
            "jqgrid.locale": {
                deps: ["jquery"]
            },
            "jqgrid": {
                deps: ["jquery", "grid.locale"],
                exports: "jQuery.fn.jqGrid"
            }
        }
    });
    
    require(["jqgrid"], function(jqgrid) {
        // ...
    });
    

    I don't know that the above code is 100% correct, but it's a rough estimation
    of what this might look like in a require-ified way.

    One interesting note is that you are going to be dependent on the JS Stack's
    version

  2. Another option might be to include your own version of jQuery, and then when
    the rest of your dependencies are loaded, you can call noConflict() on it. This
    might look like:

    <script src="{{STATIC_URL}}{{app_name}}/jquery.js" type="text/javascript"></script>
    <script src="{{STATIC_URL}}{{app_name}}/jqGrid/js/i18n/grid.locale-en.js" type="text/javascript"></script>
    <script src="{{STATIC_URL}}{{app_name}}/jqGrid/js/jquery.jqGrid.src.js" type="text/javascript"></script>
    <script>
        jQuery.noConflict();
    </script>
    

    That way, you know you can always rely on your version of jQuery, and also that
    only the jqgrid code will have access to that jQuery reference.

Hopefully the above options make sense. Let me know if I can expand or explain
something further. Also, we always love to hear what people are building, so
feel free to share 🙂

ineeman
Splunk Employee
Splunk Employee

Awesome, really looking forward to seeing it.

0 Karma

joshuamcqueen
Path Finder

Good call on the requirejs. I knew splunk was using it, but I didn't quite understand the syntax.

I'm building a sweet-sweet data gird (with a SQLite backedn) just like -> http://trirand.com/blog/jqgrid/jqgrid.html

I'll write up a blog post once it's done.

0 Karma
Get Updates on the Splunk Community!

.conf24 | Registration Open!

Hello, hello! I come bearing good news: Registration for .conf24 is now open!   conf is Splunk’s rad annual ...

Splunk is officially part of Cisco

Revolutionizing how our customers build resilience across their entire digital footprint.   Splunk ...

Splunk APM & RUM | Planned Maintenance March 26 - March 28, 2024

There will be planned maintenance for Splunk APM and RUM between March 26, 2024 and March 28, 2024 as ...