I'm started using a combination of KnockoutJS (2.2.1), SammyJS (0.7.4) and PagerJS (latest from github with jquery hashchange) to create a single page app and I've run into a problem with the routes as they do not work in Chrome Version 24.0.1312.57 m or Firefox 16.0 (for some reason it actually works in IE7).
Using sammyjs I've specified the routes that the app should react on and their corresponding actions, for example loading user data. The same routes are used in pagerjs to specify which page to display. For some reason the sammyjs code is executed but not the pagerjs code.
When updating the route, for example going from #!/ to #!/user, pagerjs doesn't switch to the new page, but data is updated as expected when switching between #!/user?uid=123 and #!/user?uid=321 . However, when removing the sammyjs code it works - the switch between pages works but data will of course not update properly.
It seems like SammyJS terminates further execution by pagerjs, but as I'm quite new to these libraries it might very well be my code misbehaving. Greatful for any insights.
The javascript code looks something like this:
var UserModel = function () {
var self = this;
self.userId = null;
self.user = ko.observable();
self.userid = ko.observable();
// Load
self.load = function(userId) {
self.loadUser(userId);
};
// Load user data
self.loadUser = function(userId) {
console.log('Loading user data');
};
// Client-side routes
Sammy(function () {
// Overview - datatables in tabs
this.get('#!/', function () {
console.log('Start page');
});
// User - details
this.get('#!/user', function () {
console.log('user page');
self.userId = this.params.uid;
self.load(self.userId);
});
}).run();
}
// Renders user info
$(document).ready(function () {
if ($('#user-info').length) {
var userModel = new UserModel();
pager.extendWithPage(userModel);
ko.applyBindings(userModel);
// Load initial data via ajax
userModel.load();
pager.Href.hash = '#!/';
pager.startHashChange();
}
$('.dropdown-toggle').dropdown();
});
And here goes the HTML with the pagerjs data-bindings:
<div class="container">
<div data-bind="page: {id: 'start'}">
Startpage
</div>
<div data-bind="page: {id: 'user', params: ['uid']}">
User page
</div>
</div>
I think I got it.
You need to add
this.get(/.*/, function() {
console.log("this is a catch-all");
});
after your last this.get. Then Sammy doesn't stop the event.
I've got it working by changing PagerJS to use the naïve history manager instead of jQuery hashchange. In other words this line:
pager.startHashChange();
was changed to:
pager.start();
As of magic it also works in IE7 even if the docs at http://pagerjs.com states it doesn't. Well, for me it does work
// 1. iff using naïve hashchange - wont work with IE7
pager.start();
As long as you include the hashchange plugin pager.start() will use it.
Is the same as the naïve, but you need to include the jQuery hashchange plugin first.
http://pagerjs.com/demo/#!/navigation/setup
Related
I really didn't know how to explain my question in the title, so I tried.
Anyways, this is my problem. I have a webpage which is basically a puzzle. The basic premise of it is that when you visit a certain link, it will trigger a function and show the next piece.
Here's one of the functions that will show the piece -
function showone() {
var elem = document.getElementById("one");
if (elem.className = "hide") {
elem.className = "show"
}
}
The reason that it's built like this, is because the pieces are constructed and placed using an HTML table, using classes to hide and show them.
What I need to do, is somehow create a URL that will trigger a new piece. For example, "www.website.com/index.html?showone" is what I'd like. This would trigger the "showone" function.
I don't know how to do this though, and after a fair bit of searching, I'm more confused than I was to begin with.
The reason I'm using JavaScript to begin with, is that the page can't refresh. I understand that this might not be possible, in which case, I'm open to any suggestions on how I could get this to work.
Thanks in advance, any suggestions would be greatly appreciated.
-Mitchyl
Javascript web application frameworks can to this for you, they allow to build web application without refresh page.
For example you can use backbonejs it has Router class inside and it very easy to use.
code is easy as :
var Workspace = Backbone.Router.extend({
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {
...
},
search: function(query, page) {
...
}
});
is also you can use angularjs it is big one that supports by Google.
Maybe this solution can help you?
$("a.icon-loading-link").click(function(e){
var link = $(e.target).prop("href"); //save link of current <a> into variable
/* Creating new icon-tag, for example $("<img/>", {src: "link/to/file"}).appendTo("next/dt"); */
e.preventDefault(); //Cancel opening link
return false; //For WebKit browsers
});
I'm getting back into web development a bit after having been kind of out of it for the past 10 years or so, and I'm overwhelmed by all the new technologies that I'm having to catch up with, ASP.NET, MVC, jQuery, SPA, Knockout, etc. I don't know the second thing about jQuery and my experience with ASP.NET is very limited. I have a little familiarity with ASP.NET WebForms, but MVC (and the rest) is totally new to me.
After seeing how many technologies there were, and not knowing which route to explore in my new project, I saw that Hot Towel seems to be a template that combines all the latest stuff into one nice package, so I decided to get the Hot Towel template and start an ASP.NET MVC4 SPA project with it.
Now I'm trying to integrate with our in-house UI framework (which has been developing without me over the past few years). I decided to try to update the Details page in the Hot Towel template to have some content. I added a simple <span>, and all's well and good. But if I try to add what I understand to be a jQuery-widget-based component (?), I get nothing. Even for the simplest test of adding content via jQuery, I get nothing:
<section>
<h2 class="page-title" data-bind="text: title"></h2>
<span>Test this</span>
<div id="testDiv"></div>
<script type="text/javascript">
$("#testDiv").append("Testing");
</script>
</section>
I see the span, but not the modified div. And I can't see any of this content in the source ("View source") or the IE9 console (not surprising given the nature of SPA, but what should I do about it?). And the Visual Studio Page Inspector seems to be totally useless (can't get past the splash screen).
What is the proper method of adding elements to the UI under the HotTowel/jQuery/MVC/SPA/KockoutJS/Breeze/Durandal model? All these new frameworks are driving my crazy.
Edit some more details: The jQuery stuff works fine when I move it to the main page of the SPA, but when I have it on the Details "page" it doesn't work. I suspect it has something to do with the SPA nature of this application and how the content of alternate views are delivered not as an entire page, but as updated content for the main page.
Edit after further investigation, I have discovered the existence of a view model named "detail" which is probably related to this detail view code I have posted. This is the code from the view model:
define(['services/logger'], function (logger) {
var title = 'Details';
var vm = {
activate: activate,
title: title
};
return vm;
//#region Internal Methods
function activate() {
logger.log(title + ' View Activated', null, title, true);
return true;
}
//#endregion
});
The script is probably executing but cannot find the div. To correct manipulate div put your jquery code to in a function and trigger that function using attached/compositionComplete callback for duranadal 2.0 or viewAttached callback for durandal 1.x
1.x link - https://github.com/BlueSpire/Durandal/blob/master/docs/1.2/Composition.html.md#view-attached
2.0 link - http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks/
// in your detail view model, if using durandal 1.x
define(['services/logger'], function (logger) {
var title = 'Details';
var vm = {
activate: activate,
title: title,
viewAttached : function(view){
// view is the root element of your detail view and is passed in
// by durandal
$(view).append("Testing");
}
};
return vm;
//#region Internal Methods
function activate() {
logger.log(title + ' View Activated', null, title, true);
return true;
}
//#endregion
});
// in your detail view model, if using durandal 2.0, you have two options
define(['services/logger'], function (logger) {
var title = 'Details';
var vm = {
activate: activate,
title: title,
attached : function(view, parent){
// view is the root element of your detail view
// and is passed in by durandal
$(view).append("Testing first method");
},
compositionComplete: function(view, parent){
// view is the root element of your detail view
// and is passed in by durandal
$(view).append("Testing second method");
}
};
return vm;
//#region Internal Methods
function activate() {
logger.log(title + ' View Activated', null, title, true);
return true;
}
//#endregion
});
I have the following server-side URL mappings defined:
/main/item1
/main/item2
I've added SammyJS routing support so that I am able to do the following:
/main/item1#/ /* main view */
/main/item1#/topups /* topup view */
I've set up SammyJS like so:
s.app = Sammy(function() {
this.get('#/topups', function() {
console.log('Initializing topups view.');
});
this.get('#/', function() {
console.log('Initializing main view.');
});
});
The problem is, I have a summary section in my page that redirects to the topup view of a different "item". E.g., I am at the url /main/item1#/, and in this page, there exists a tag item 2's topups.
I expect to be redirected (page refresh) to the new URL, however, it seems like SammyJS is intercepting the /main/item2#/topups call and simply running the this.get('#/topups') route I've defined.
I expect that since the URL paths before the hash, /main/item1 and /main/item2 are different, the SammyJS routing won't be triggered.
Is there a way to prevent this behavior from happening in SammyJS?
I don't know much about Sammy but I can tell you from the way any router behaves, is that it catches the first match in the routing possibilities, and so, anything that ends with #/topups will be considered the same as long as it's after the hash sign.
so you better define the router this way:
this.get('#/topups/:item', function() {
console.log('Initializing topups view for item: '+ item);
})
and then call the pages with URLs like:
item 2's topups
I hope this is what you're looking for
I'm pretty sure using the full URL redirect you.
Item 2 top ups for the lazy coder
However, that will cause the page to reload. If you modify Labib's answer you can have an even better solution:
this.get('#/topups/:item', function () {
console.log('Doing some post processing on current item');
console.log('Now redirecting you to ' + this.params.item);
window.location.href = 'http://example.com/menu/# + this.params.item +#/topups';
});
Again this will cause the page to reload, but if you do not mind that, then just either method.
NOTE: Sammy will also check form submission for you. This trips me up EVERY time I use it. This post has the solution.
I'm porting a web service into a single-page webapp with Backbone. There is a basic layout consisting on a header, an empty div#content where I'm attaching the views and a footer.
Every route creates the corresponding view and attachtes it to div#content replacing the view that was rendered before with the new one.
I'm using require.js to load the backbone app and it's dependencies.
All Backbone code is pretty small, only one file as I'm only using a router and a view.
This AMD module depends on a util.js file exporting functions that are used in the views.
After a view is created and rendered, It executes the utilities (jquery stuff, ajax, etc) it needs from util.js.
The problem is that when I render a view, it's utilities get called, and when I navigate to another route, and a new view is created, the new view's utilities are called now, but the older view's utilities are still running.
At some point, I have utilities from like five views running altogether, causing conflicts sometimes.
It's clear than my approach is not good enough, as I should have a way to stop/start utilities functions as some kind of services.
I'll paste relevant code that shows my current approach:
require(["utilities"], function(util) {
...
Application.view.template = Backbone.View.extend({
el: "div#content",
initialize: function(){
this.render();
},
render: function(){
var that = this;
// ajax request to html
getTemplate(this.options.template, {
success: function(template) {
var parsedTemplate = _.template( template, that.options.templateOptions || {});
that.$el.html(parsedTemplate);
// execute corresponding utilities
if(that.options.onReady) {
that.options.onReady();
}
},
error: function(template) {
that.$el.html(template);
}
})
}
});
...
Application.router.on('route:requestPayment', function(actions) {
var params = { template: 'request-payment', onReady: util.requestPayment };
var view = new Application.view.template(params);
});
...
});
util.requestPayment consist of a function having all stuff needed to make template work.
I'm confused about how should I handle this issue. I hope I was clear, and any suggestions or help will be appreciated.
EDIT: utilities.js snippet:
...
var textareaCounter = function() {
$('#requestMessage').bind('input propertychange', function() {
var textarea_length = 40 - $(this).val().length;
if(textarea_length === 40 || textarea_length < 0) {
$('#message-counter').addClass('error').removeClass('valid');
$("#submitForm").attr('disabled', 'disabled');
}
else if(textarea_length < 40 && textarea_length > 0) {
$('#message-counter').removeClass('error');
$("#submitForm").removeAttr('disabled');
}
$('#message-counter').text(textarea_length);
});
}
...
var utilities = utilities || {};
...
utilities.requestPayment = function() {
textareaCounter();
initForm();
preventCatching();
requestPaymentCalcFallback();
};
...
return utilities;
...
I would suggest that you should store reference to the currently active view somewhere in your app.
You create a new view here :
var view = new Application.view.template(params);
but you have no access to this variable afterwards. So it exists but you can't stop/delete/get rid of it.
What we normally do is to have a Parent App class which initializes the whole app and manages everything. Your every module in requirejs would be depenedent on it. When a new route is navigating, you ask the Parent App class to change the view. It will delete the old view, create a new one, populate div#content and then store the reference of it.
I think when you delete the old view, all the utilities will stop responding to it.
If you still have the issue with events being called, then you might need to use stopListening event binders before deleting the view reference.
I'm trying use the history backbone root but it doesn't work fine on IE (or other browsers which don't support history api).
My webapp has this map, where each module makes a request, but actions should call a function:
site/moduleA/
site/moduleA/action1/ID
site/moduleB/
site/moduleB/action1/ID
mapping:
var MyRouter = Backbone.Router.extend({
routes: {
"moduleA/": "homeA",
"moduleA/action1/:id": "action1",
// ...
}
}
var app = new MyRouter();
Backbone.history.start({pushState: true});
I'm navigating using this:
app.navigate('moduleA/',{trigger:true});
or
app.navigate('/moduleA/action1/4334',{trigger:true});
(I'm getting links click events and calling navigate(link.href,{trigger:true}) )
Every is working fine on Chr/FF (browsers with history api support), and the url is updated in the browser and the function is call.
However, in IE the url is replaced by this hash format: site/#moduleA/
In order to solve that I've tried set the root in history.start
Backbone.history.start({pushState: true, root:'/moduleA/'});
But, now IE replace the url using this format: site/moduleA/#moduleA/ or site/moduleA/#moduleA/action1/432432.
So, Why IE is repeating the root in the url ?
How can I solve this?
Thanks in advance
By setting root to '/moduleA/' you are telling backbone to use site/moduleA as the root, this is the expected behavior.
Rememeber in backbone
routes: {
"moduleA/": "homeA", // #moduleA/
"moduleA/action1/:id": "action1" // #moduleA//action1/:id
}
is different from
routes: {
"/moduleA/": "homeA", // #/moduleA/
"/moduleA/action1/:id": "action1" // #/moduleA//action1/:id
}
its good to keep this in mind when using app.navigate.