Refresh hasherJS/crossroadsJS routes - javascript

I’m utilizing crossroadsJS and hasherJS on an SPA powered by handlebarsJS. In retrospect I probably should have built the whole thing on Ember, but this late in it’s not wise to start over.
I have hash routes set up that switch my handlebars templates in and out based on whatever was clicked. I’m able to move backwards and forwards between the routes with no problem. However, if I refresh the page, I’m always brought back to my #/home view. I’ve done some research into window.onhashchange and window.onbeforeunload, but none of these seem to be solving the issue.
I have a very specific way of how I’m handling my views. I have a global views object constructed like so:
views.procedures = function (template) {
this.initialize = function () {
this.el = $('<div/>');
this.el.attr('id', 'procedures');
this.el.on('click', '.returned-list li a', this.toResults); // list of API objects by name
};
this.render = function () {
var parts = {
title: 'Procedures View',
};
this.el.html(template(parts));
return this;
};
this.toResults = function () {
cache.justClicked = $(this).attr('data-id');
crossroads.addRoute('procedures/{name}', function () {
api.endpoints.procedure(cache.justClicked); // constructed elsewhere
});
};
this.initialize();
};
I mention this because I can’t simply add in JQuery listeners for any type of hash changes in a $(document).ready() or $(window).onhashchange
I’ve read through both the hasherJS and crossroadsJS documentations multiple times, but I’m not able to think of anything to solve the refresh issue. Thanks in advance for any help.

I would need more information from you, but I'm assuming you're doing
var DEFAULT_HASH = 'home';
if (! hasher.getHash()) {
hasher.setHash(DEFAULT_HASH);
}
This is basically saying your default route is always going to be your home route, which is why you're seeing it when you refresh your current route. Try the following:
var DEFAULT_HASH = 'home', url = hasher.getBaseURL();
if (hasher.getURL() === url) {
hasher.setHash(DEFAULT_HASH);
}
This will check to see what your base URL is (the one that's loaded when you first visit the page) and append a #/home route to the base URL, allowing you to refresh your currently viewed route.
Hope this helps.

Related

Notify main page of variable changed by external js

I'm attempting to create a modular sign in script for some webpages I'm developing. In short, I load the script on the main page, fire the main signIn function from a button press, and an overlay div is created on the main page which is managed by the external signIn.js. The external js sets some sessionStorage variables that will be utilized in the main page.
The hope for modularity would be to have signIn.js handle the authentication from the database and have the main page do with the process of signing in as needed (in this specific instance, it gives users access to their projects). Ideally, the sign in will not force a refresh of the main page due to other project goals.
The problem I'm encountering, is how do I notify the main page that the user has signed in without destroying any sense of modularity?
On top of other efforts, the most hopeful was attempting to create a custom event on the main page's document using $(document).on('userSignedIn', function() {...}); but signIn.js apparently cannot trigger this event.
Any suggestions for how to accomplish this or am I just going about this entirely wrong?
EDIT:
So, this was definitely a scope related issue I was experiencing. To flesh out the process, if anyone finds it relevant, signIn.js adds an overlay div to mainPage.html. $("#signInContainerDiv").load("signIn.html") is used to load the sign in form into the page. It turns out, when I was trying to reference $(document), it was using signIn.html's document, and not mainPage.html's. Upon that realization, I just created a div (signInNotify) on the mainPage that I bind the event to ($("#signInNotify").on("userSignedIn", function() {...});) and trigger it in signIn.js.
My own inexperience has conquered me, yet again.
jQuery can help you out when it comes to this. Here's an example from the main page for trigger
$( "#foo" ).on( "custom", function( event, param1, param2 ) {
alert( param1 + "\n" + param2 );
});
$( "#foo").trigger( "custom", [ "Custom", "Event" ] );
jQuery Page Reference
Another solution is to use some library like amplify.js, it has publish/subscribe functionality which can be useful for implementing the "observer pattern". You could also implement your own library for that, the code could be something like this:
// the implementation
function Notify () {
this.listeners = {};
}
Notify.prototype.subscribe = function (event, callback, context) {
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push({ callback: callback, context: context || null});
};
Notify.prototype.publish = function (event/*, args...*/) {
var args = Array.prototype.slice.call(arguments, 1);
(this.listeners[event] || []).forEach(function (x) {
x.callback.apply(x.callback.context, args);
});
};
// usage:
// an instance, or can be implemented as a singleton
var global_events = new Notify();
// wherever you want to be notified of login events
global_events.subscribe('login_success', function () {
// do something with the arguments
}, myContext/*optional*/);
// after success login
global_events.publish('login_success', user_credentials, other_data);
// and all subscribers (listeners) will be called after this
I have used that code for similar purposes and also used amplifyjs a couple times, you can read more about Amplify Pub/Sub.

Meteor: onhashchange does not fire

I have a weird situation. It seems Meteor with Iron::Router is overriding somewhere the onhashchange event however I was unsuccessfully to track it down.
Basically if I listen for that event, it never fires for some reason. I looked and searched everywhere and can not even find any reference to onhashchange in the Meteor code base.
if(Meteor.isClient) {
window.addEventListener('hashchange', function() {
alert('changed');
});
}
This never fires - although the event is properly registered. In plain vanilla it works fine .. so I assume it's somewhere being overwritten.. any insights will be appreciated.
http://jsfiddle.net/L2dj3o7n/
Oh one more thing, this is how my URL's look like right now for testing:
http://localhost:3000/#/workflow
http://localhost:3000/#/settings/account
http://localhost:3000/#/group/add
etc
From the Router Parameters section of the Iron Router guide:
If there is a query string or hash fragment in the url, you can access
those using the query and hash properties of the this.params object.
// given the url: "/post/5?q=s#hashFrag"
Router.route('/post/:_id',
function () {
var id = this.params._id;
var query = this.params.query; // query.q -> "s"
var hash = this.params.hash; // "hashFrag"
});
Note: If you want to rerun a function when the hash changes you can do
this:
// get a handle for the controller.
// in a template helper this would be
// var controller = Iron.controller();
var controller = this;
// reactive getParams method which will invalidate the comp if any part of the params change
// including the hash.
var params = controller.getParams();
getParams is reactive, so if the hash updates, the getParams() call should invalidate and trigger a new computation for whatever you're using it for. For instance, if you wanted to dynamically render a template depending on the hash value, you should be able to do something like this...
HTML:
<template name='myTemplate'>
{{> Template.dynamic template=getTemplateFromHash}}
</template>
JS:
Template.myTemplate.helpers({
getTemplateFromHash: function() {
var hash = Iron.controller().getParams().hash;
... // do whatever you need to do with the hash to figure out the template to render
}
});

Backbonejs is calling collection's fetch method in another function good idea? If not what should i use?

I am trying to implement endless scrolling with Backbonejs. My view initializes a collection and calls fetch fetch function.
My view
var app = app || {};
app.PostListView = Backbone.View.extend({
el: '#posts',
initialize: function( ) {
this.collection = new app.PostList();
this.collection.on("sync", this.render, this);
this.collection.fetch();
this.render();
},
render: function() {
/*render posts*/
}
});
In my page I added the following code. It checks if the the user at the bottom of the page. If yes then it checks if the view is initialized. If yes then call that view fetch function of the view's collection object.
var app = app || {};
$(function() {
var post_view;
$(window).scroll(function() {
if(($(window).scrollTop() + $(window).height() == getDocHeight()) && busy==0) {
if(!post_view){
post_view = new app.PostListView();
} else {
post_view.collection.fetch();
}
}
});
});
So far this code is working. I am not sure if this is the right approach or not?
It's not a bad option; it works, and Backbone is making that collection available for you. But there's a couple of other options to consider:
Move that collection.fetch into a method getMoreItems() inside your PostListView, and call it within your else block. That way you're encapsulating your logic inside the view. Your app is more modular that way, and you can make your scrolling smarter without updating the rest of your app.
Move the scroll listener inside your PostListView. I'd probably put this within your PostListView's initialize function. Again, this reduces dependencies between the various parts of your app - you don't have to remember "Whenever I create a PostListView, I must remember to update it on scroll." By setting that event listener within the PostListView itself, all you have to do is create it. Backbone's general philosophy is to have small, independent components that manage their own state; moving the event listener inside would fit with that.

Best way to start a backbone.js app with multiple views and routes

I have started to use/learn backbone.js and come across something i don't quite understand.
If i have a page that on load should load a couple of items on every page, like a menu. Most of the examples i see fetches the collections, and renders the view in the router. But then it's locked to that route.
What is the best way to load some views for every page if it's not already loaded?
I use the following pattern:
var App = { // a 'namespace' to hold references to my App's variables to avoid globals
UI : {} // references to UI components go here
}
// initialize stuff once the DOM is ready.
$(function(){
App.UI.menu = App.UI.menu || new MenuView({ el : '#menu' }); // the || is in case it's been defined elsewhere. Just me being cautious;
App.UI.footer = App.UI.footer || new FooterView({ el : '#footer' });
App.router = App.router || new Router();
Backbone.history.start()
});
The above requires that MenuView and FooterView call their render methods when initialized. You need to have elements with id="menu" and id="footer" in your document as well.
var MenuView = Backbone.View.extend({
initiaize : function(){
this.render();
},
render : function(){
// you'd probably use a template and iterate over an array of links.
this.$el.html('<ul><li>Menu item 1</li><li>Menu item 2</li></ul>');
}
});
Try this small solution.
view = new BackboneView({ collection: yourCollection});
$("body").append(view.render().el);
Add This in your Backbone View
render: function() {
$(this.el).html(this.headerTemplate);
return this;
}
I guess, firstly, it is better to write backbone.history.start(), after then you can hand each url changes. When you call navigate router work, but there are two way to call navigate (may be you know about it)
1 -> yourRouterName.navigate("newUrl") : //it doesn't use router, it only added newUrl to `baseUrl`
2 -> yourRouterName.navigate("newUrl", true) : //it added newUrl to `baseUrl`, router works now.
The two way useful, but you should know how to use it

How do I load an html file according to a route in sammy js?

I don't have enough experience with javascript and jQuery development so I was wondering if someone could tell me how to create a function that uses sammy.js and HTML templating.
This is the working code I have right now:
<script>
;(function($) {
var app = $.sammy(function() {
this.get('#/', function() { $('#content').text(''); });
this.get('#/home', function() { $('#content').load('main.html'); });
});
$(function() { app.run() });
})(jQuery);
</script>
This code puts the main.html's content when I go to or click on ...html#/home but I am looking for a javascript function that just figures out the end point of the URL (the word after ..#/ ) and gets the html page of that same name.
In pseudo-code:
if( somethingWasClicked and uses sammy.js )
loadContent();
function loadContent() {
endpoint = figureOutEndPoint();
$('#content').load(endpoint.html);
}
Thanks for the help
You just need a route with a parameter in it:
this.get('#/:page', function() {
$('#content').load(this.params['page'] + '.html');
});
Make sure that's after your other routes or it will get called for almost everything (such as #/home which you're already handling).
You can, of course, put other parameters in the routes:
this.get('#/:where/:is/:pancakes/:house', function() {
// Do things with this.params['where'], this.params['is'],
// this.params['pancakes'], and this.params['house'].
});
You just need to make sure you get the order right or the routes can fight each other. Routes are matched in the same order that they're added in so the more general catch-all routes usually go at the bottom.

Categories

Resources