Knack, under the hood - understanding handleChangeFilters

Ho to all,

I’m trying to better understand how filters work under the hood. The KTL works pretty well, but it always refreshes the whole page instead of only the view involved when a User Filter is clicked.

I found this code, which does exactly what I want, but I don’t understand the var u = new t.Model line. What is t? I breakpoint execution there and I can see the object, but can’t find a way to replicate this in my code. All the rest is ok, but not the t.

Help!
Normand

handleChangeFilters: function(e) {
    var n = this
        , i = n.this
        , r = Knack.getSceneHash()
        , a = e && e.rules && e.rules.length
        , o = e && e.length;
    if (a || o) {
        var s = e
            , c = {};
        c[i.model.view.key + "_" + n.index + "_filters"] = encodeURIComponent(JSON.stringify(s)),
            (l = Knack.getQueryString(c)) && (r += "?" + l)
    } else {
        var l;
        (l = Knack.getQueryString(null, [i.model.view.key + "_" + n.index + "_filters"])) && (r += "?" + l)
    }
    i.menu_filters_changed = !0,
        Knack.router.navigate(r, !1),
        Knack.setHashVars(),
        Knack.showSpinner();
    var u = new t.Model;
    u.url = i.model.data.url + "/" + n.index,
        e && (u.url += "?filters_" + n.index + "=" + encodeURIComponent(JSON.stringify(e))),
        i.model.view.scene && i.model.view.scene.scene_id && (u.url += "&" + i.model.view.scene.slug + "_id=" + i.model.view.scene.scene_id),
        u.fetch({
            success: function (e, t) {
                Knack.hideSpinner(),
                    i.renderReport(n, t.report, n.index + 1),
                    i.triggerRecordsRendered()
            }
        })
},

Normand,

It seems you are looking at the handleChangeFilters function for reports. It’s easier if you take a look at the equivalent function for tables:

regular handleChangeFilters fn
handleChangeFilters: function(e, t) {
    Knack.showSpinner();
    var n = Knack.getSceneHash()
        , i = e && e.rules && e.rules.length
        , r = e && e.length;
    if (this.model.view.map_filters && (i ? e.rules = e.rules.concat(this.model.view.map_filters) : e = r ? e.concat(this.model.view.map_filters) : {
        rules: this.model.view.map_filters,
        match: "and"
    }),
    i || r) {
        var a = {};
        a[this.model.view.key + "_filters"] = encodeURIComponent(JSON.stringify(e)),
        this.model.view.pagination_meta.page = this.model.view.source.page = 1,
        a[this.model.view.key + "_page"] = 1,
        (o = Knack.getQueryString(a)) && (n += "?" + o)
    } else {
        var o;
        (o = Knack.getQueryString(null, [this.model.view.key + "_filters"])) && (n += "?" + o)
    }
    Knack.router.navigate(n, !1),
    Knack.setHashVars(),
    this.model.setFilters(e),
    t ? t() : "calendar" == this.model.view.type ? this.renderRecords() : (this.model.data.on("reset", this.hideLoading, this),
    this.model.fetch({
        page: 1
    }))
}

So, to set filters yourself, here’s a more readable, basic equivalent:

/**
 * @typedef {Object} KnFilterRule
 * @property {string} field
 * @property {string} operator
 * @property {string} value
 */

/**
 * @typedef {Object} KnFilters
 * @property {'and' | 'or'} match
 * @property {KnFilterRule[]} rules
 * /

/**
 * Sets new filter for given view.
 *
 * @param {string} viewKey
 * @param {KnFilter | KnFilterRule[]} filters
 */
const updateFilters = (viewKey, filters) =>
  new Promise((resolve) => {
    const sceneHash = Knack.getSceneHash();
    // getQueryString not only gets the query string from current hash vars, but **also** sets query string params when provided an object
    const queryString = Knack.getQueryString({ [`${viewKey}_filters`]: encodeURIComponent(JSON.stringify(filters)) });
    // navigates to new query string via Backbone router
    Knack.router.navigate(`${sceneHash}?${queryString}`, false);
    // updates internal hash vars from current url
    Knack.setHashVars();
    // set new filters on view's model
    Knack.models[viewKey].setFilters(filters);
    // refetch view data, using new filters
    return Knack.models[viewKey].fetch({
      success: () => resolve(),
    });
  });```
3 Likes

Dumb question here @hmnd - where are these functions located?

I found them in the Sources view of the DevTools window (F12). I clicked Pause (F8 = pause/resume execution) a few times until I saw a weird filename like k_796ffd2fc065b2defb109c89d6d560f191cacf98.js This is Knack’s code (BTW, to see your code, pause until you see VMxxxx as the filename).

I pretty-printed (with the {} icon) and saved this Knack’s code file to my HD and did some regex searches on various strings such as handle.*sort and handle.*filter

This led me to the functions I was interested in.

Normand

Hi David,

Wow! That’s very interesting. I can’t wait to test it.

I wonder if it’s possible to get the normal (non-minified) version of Knack’s code. After all, it can’t be that much of secret if we already have it under our eyes - just a bit hard to read and understand. If I could get this, the KTL would jump to a whole new level.

Cheers,
Normand

Why do you have three backticks ``` at the end?
If I remove one, I don’t get any error anymore in Visual Studio, but I still don’t see the reason for this.

ND

DUDE!!! You’re amazing - it works perfectly :stuck_out_tongue_winking_eye:

For all those interested to see the solution (hard-coded for quick testing), here it is:

var filters = {
    "match": "and",
    "rules": [
        {
            "field": "field_28",
            "operator": "higher than",
            "value": prompt('value'),
            "field_name": "Prod Qty"
        }
    ]
};

const updateFilters = (viewKey, filters) =>
    new Promise((resolve) => {
        const sceneHash = Knack.getSceneHash();
        // getQueryString not only gets the query string from current hash vars, but **also** sets query string params when provided an object
        const queryString = Knack.getQueryString({ [`${viewKey}_filters`]: encodeURIComponent(JSON.stringify(filters)) });
        // navigates to new query string via Backbone router
        Knack.router.navigate(`${sceneHash}?${queryString}`, false);
        // updates internal hash vars from current url
        Knack.setHashVars();
        // set new filters on view's model
        Knack.models[viewKey].setFilters(filters);
        // refetch view data, using new filters
        return Knack.models[viewKey].fetch({
            success: () => resolve(),
        });
    });

updateFilters('view_2248', filters)
    .then(function () {
        console.log('Done filtering');
    })
    .catch(function () {
        console.log('Error filtering');
    })

I’ve bound the call to updateFilters from the F2 hotkey and boom! I saw the table update itself properly without the whole page refresh! Soooo much better.

Normand

The tables are solved - thanks to David.

Now going back to my first post, to support the charts in a report view, I still need to understand the var u = new t.Model; line.

On a breakpoint, I see that t is in a closure and u contains VERSION: “1.1.2”.

Searching for 1.1.2 led me to some code related to Backbone:

    void 0 === (r = function(e, t) {
        var n;
        return this.jQuery = e,
        this._ = t,
        (n = this).Backbone = function(e, t, n, i) {
            var r = e.Backbone
              , o = []
              , s = o.slice;
            t.VERSION = "1.1.2",
            t.$ = i,
            t.noConflict = function() {
                return e.Backbone = r,
                this
            }
            ,
            t.emulateHTTP = !1,
            t.emulateJSON = !1;
            var c = t.Events = {
                on: function(e, t, n) {
                    if (!u(this, "on", e, [t, n]) || !t)
                        return this;
                    this._events || (this._events = {});
                    var i = this._events[e] || (this._events[e] = []);
                    return i.push({
                        callback: t,
                        context: n,
                        ctx: n || this
                    }),
                    this
                },
...

How can I get the equivalent of var u = new t.Model; ???!!!
I just don’t get it. That “t” is so obscure…

Normand

This is great! I’ve just updated the KTL and the tables are refreshed smoothly - at last.
The User Filters also store the sort column/order, the records per page value and even the search text.

I strongly recommend you try this feature, it’s awesome.

If you’re curious to see the final code, search for function onFilterBtnClicked in the KTL.js file. Finally, I set all the parameters and invoke fetch only once for all elements of the filter, which is pretty cool. I wasn’t sure this would work, but it does.

Thanks again David (aka hmnd) for your help, I learned a lot during the last two days.

Normand

1 Like

Hi Normand,

Can I use this for Map views too? I.e. create public filters that can be applied on the rendered records (properties/ addresses) once the initial map search is done?

Thanks,
Chrislee