Nearly completed transitioning from Iron to Flow router... A few persisting issues

I have a couple questions regarding my transition to FlowRouter that I haven’t been able to figure out myself, hopefully someone can assist!

FYI, My entire project was designed with the same coding conventions/methods as taught in Discover Meteor.

  1. Pagination is no longer working. Before it was handled by the router, but now that the router’s logic has been moved client side, I do not think that would work for pagination. Let’s say I wanted to sort my collection of Products by “top 10 cheapest price” - On client side I would need to send the entire collection in order for it to find which products are cheapest.

With FlowRouter, is there a way to control which items get published, so that I could send only the 10 cheapest products to client, and not have to send the entire collection?

  1. My Collection.update functions stopped working after the router update. Both on my “Edit” pages as well as my “Notifications” pages. These were both were based on DiscoverMeteors “Edit Post” and “Comment Notification” systems.

As an example…

ProductCollection.update(currentProductId, {$set: productProperties}, function(error) {
if (error) {
throwError(error.reason);
} else {
console.log(‘success’);
}
});

I removed the route redirect from “else” for testing purposes. That function is ran by the Edit code, and the “success” message is logged in chat, so it seems the code is not detecting any problems, but there are absolutely no changes to the product. Any ideas on how to repair this issue?

  1. Access denied page stopped working. Again, this was designed similar to DiscoverMeteor’s basis. Accounts system is still working for login & database functions. But the access denied routes are broken.

I seen a post on the kadira page on how to add verification on a template level, but I seen most users saying that’s not a good idea. I tend to agree, having to add logic to every template sounds troublesome.

Could anyone advise an elegant way of getting the access denied pages working again with FlowRouter?

Hope someone can help! Thanks!

You could pass arguments (limit, sort, filter, etc.) to your publication, and have it rerun reactively when you change the value of the passed arguments - for example like this:

Template

Template.foo.onCreated(function(){
    this.state = new ReactiveDict()

    this.state.set('bar.skip', 0)
    this.state.set('bar.limit', 5)
    this.state.set('bar.sort', {createdAt:-1})

    this.autorun(()=> this.subscribe('bar',
        this.state.get('bar.skip'),
        this.state.get('bar.limit'),  
        this.state.get('bar.sort')
    ))
})

Publication

Meteor.publish('bar', function(skip, limit, sort) {
    return Bars.find({}, {skip, limit, sort})
})

you can now reactively paginate with infinite scroll or traditional pagination based on changing state when you, for example, scroll to the bottom of your list of bars or when you click to a new page of bars :slight_smile:

I see two simple approaches to this problem, depending on how specific needs you have:

###1. Make another layout that constrains access, and optionally pass another argument in the router that can restrict access further

in my example, I have another field on users, indicating their role if logged in. This would be trivial to manipulate to restrict access using other properties. Remember, this has nothing to do with security, but simply serves to only show what is relevant to a user. If you want to secure layouts and pages (not just data), you have to work with some kind of server side rendering, which is far more complicated AFAIK.

//public route
FlowRouter.route('/', { name: 'home', action() {
    BlazeLayout.render('publicLayout', {page: 'home'})
}})

//only for logged in users with the role 'administrator'
FlowRouter.route('/secret', { name: 'secret', action() {
    BlazeLayout.render('privateLayout', {page: 'secret', role:'administrator'})
}})

the private layout would look something like this:

<template name='privateLayout'>
    {{#if currentUser}}
        {{> navigation}}
        <div class='ui container'>
            {{#if role}}
                {{#if equals role currentUser.role}}
                    {{> Template.dynamic template=page}}
                {{else}}
                    {{> notFound}}
                {{/if}}
            {{else}}
                {{> Template.dynamic template=page}}
            {{/if}}
        </div>
            {{> footer}}
    {{else}}
        {{> login}}
    {{/if}}
</template>

###3. Implement logic into the page to constrain access
you can probably imagine how this would go - I won’t describe more here :smile:

you can obviously use any combination of these techniques - I usually only pass constraints through the render function in the router, and have layouts with embedded constraints. Good luck :sunny:

Goatic,

Thank you for sharing these! But I am having some problems with the code you provided.

ReactiveDict is throwing an error:

ReferenceError: ReactiveDict is not defined
at null. (product_list.js:9)

As well as the autorun (()=> line is throwing an error.

Also the publish line - .find({}, {skip, limit, sort}) - is throwing an error “Shorthand property names are not supported by current JavaScript version”.

I’m assuming I’m missing some packages to make this function?

TLDR; meteor install reactive-dict ecmascript es5-shim

well, first of all you need to install reactive-dict

apart from that, I assumed you had es5-shim and ecmascript packages installed? also, what Meteor version are you running? I’m assuming a flavor of 1.2 or 1.3-beta?

about the publish line, here’s the docs. If you don’t have ES6 features enabled (with ecmascript), you can’t map like this:

Meteor.publish('bar', function(skip, limit, sort) {
    return Bars.find({}, {skip, limit, sort})
})

and have to

Meteor.publish('bar', function(skip, limit, sort) {
    return Bars.find({}, {skip:skip, limit:limit, sort:sort})
})

instead.

Goatic,

Awesome, it’s working now, thanks for the help!

One final question: Even though the code is working now, I use WebStorm IDE and the “this.autorun” line, specifically here: “()=>”. I’m assuming it’s not recognizing one of the packages correctly or possibly its recognizing wrong version of ecmascript? Could you please let me know where that code comes from so that I could try to get WebStorm to recognize the expression?

Thanks again!

Here’s the heavy duty description from Mozilla…

try this:

Template.example.onCreated(function() {
    console.log(this)
}

Template.example.onCreated(() => {
    console.log(this)
})

you’ll notice ‘this’ represents different values in each example, as ()=>{} functions use the ‘this’-context of outside the function, whilst regular function(){} functions have their own context.

in this case, if you want to be able to this.subscribe inside the template, or attach something else to the context of the template instance, you’ll have to use a regular old-fashioned function.

However, with arrow functions, you can use the context of the outer scope, which is useful for a number of cases - for example:

Template.example.onCreated(function() {
    this.call('test', (error, test) => {
        if(error)
            return console.log(error)
        this.test = test
    }
})

my callback function above fetches a test object, and after some time attaches it to the template instance, because this inside an arrow functions still referes to the template instance context.

this would fail if I did it with a regular old-fashioned callback function:

Template.example.onCreated(function() {
    this.call('test', function(error, test) {
        if(error)
            return console.log(error)
        this.test = test //FAILS because 'this' is enclosed by the function.
    }
})

also, when you don’t care about the context within the function, it’s very nice to be able to use arrow-functions as shorthands :sunny: there’s a lot less boilerplate to doing that sometimes… For example if you are making global helpers in Blaze you can turn

Template.registerHelper('equals', function(x,y){ return x===y })

into

Template.registerHelper('equals', (x,y) => x===y)
2 Likes

Goatic,

Thank you so much for your help! Awesome advice =)

I thought of yet another “final question”… using the pagination code you suggested, how would you query to see if you are at the end of the collection?

I haven’t really done that myself :sunny: hack away, and tell me if you come up with something clever!