Currently, the website I'm working on uses knockout for our frontend framework. I want to incorporate Turbolinks with multiple knockout viewmodels across different pages. The problem I'm running into is that since turbolinks doesn't actually change the page, bindings get applied multiple times which results in an error.
So far here is what I have tried.
I found this article which describes exactly what I want to do but doesn't necessarily work.
His solution is to replace the ko.applyBindings with
window.applyTurboBindings = function(node, viewModelAccessor) {
return $(document).one("page:change", function() {
return ko.applyBindings(viewModelAccessor(), node);
});
};
and use it with this
applyTurboBindings(document.getElementById('my-node'), function() {
return new MyViewModel();
});
page:change is no longer a method used with turbolinks according to their documentation. So I tried it using turbolinks:load (which fires once after the initial page load, and again after every Turbolinks visit) and turbolinks:visit (fires immediately after a visit starts). That wasn't working either. The event never gets triggered when I call the method applyTurboBindings so no bindings get applied.
Here is a fiddle with a basic setup of what I'm doing. fiddle
When I run this through my debugger, the applyTurboBindings never catches the event (turbolinks:load) that is firing.
One idea I had was to use something like this:
$(document).one("turbolinks:load", function () {
// execute all js for page here
});
instead of the typical
$(function(){
// execute all js for page here
});
But I couldn't get that working either because bindings were still getting applied multiple times.
So my question is how on earth do I get this working for multiple viewmodels so the bindings don't break? Should I be organizing my js differently? Is there a simple solution I'm not seeing or is this just not a good idea use both together?
This is my first question on Stack Overflow by the way so if I'm missing anything or I didn't make my question clear enough please let me know. Thanks for your help.
Related
I've got a question that searching did not actually yield any results. I apologize if this has been asked in the past.
I'm trying to define an event listener, and immediately remove the JS after defining the event listener. The problem is, I want the removal to take place prior to the event taking placing. Is this possible? I read in the JQuery 1.4 documentation that detach() is like remove, but maintains JQuery data. Is this a viable solution? Are there downsides to removing the code which places an event listener prior to the event taking place?
The code I'm trying to remove is dynamically generated code. Doing something like this:
<script id="12341234">
$( function() {
$('#test').click( function() {
alert('Hello');
});
});
</script>
Later in the script:
$('#12341234').remove();
That works fine for removing the script, it just doesn't fire the event listener (which makes sense).
The reason I'm trying to do this is for a very small amount of added security. Sensitive fields are already masked, no one can gain access to anything sensitive per se. I just have PHP generating a decent amount of JS, and I don't want the temptation of "inspect element". If they do actually see it, it's not a huge deal. I'd just prefer to remove it if possible.
Any help is much appreciated. Thank you in advance.
In case anyone is wondering, I figured out the answer to this.
It functions just as I theorized: It removes the script tags and everything in between, but the event listener remains intact. See below for example:
<script id="testscript">
$( function() {
$('#testbutton').click( function() {
alert('Hello!');
});
});
$('#testscript').detach();
</script>
This will keep your event-listener, while preventing your JS from being displayed on inspect.
A quick note, if someone is using any sort of debugging tools which can step through rendering, they can EASILY see the code before it's ever removed. However, if you simply don't want the average user to see the JS, this will work.
Sensitive data will still need masked or dealt with appropriately.
I still don't know the answer to the question as to whether or not detach() has a negative impact, or if it should be avoided for this use-case. However, I did some testing and it doesn't appear to have much performance impact versus remove(). We're talking 1% slower or less. I also dug a bit more through documentation, and reaffirmed my initial believe that my use-case is basically a standard use-case for detach(). There doesn't seem to be any downside to using it. I may be wrong, and if so feel free to correct me.
I think this question is asked, but I am surprised to see that dojo is not behaving as per the docs. I want some div to be changed with particular class. So I decided to use
dojo.ready(function(){
});
But that was running before the page was completely loaded. Then I used addonload() function. That too gave the same result. Finally I ended up doing something like this
require(["dojo/domReady"], function(domReady) {
domReady(function () {
setTimeout(function(){
setAfrobeat();
},500);
});
});
That is working fine, but some times I see a blink as there is delay, and very a few times this also doesn't work. If I increase that timeout to 1000 it works always, but user can see the content modification. Any perfect way like I used to do in jquery's document.ready
Regards
Aadam
The way you are loading domReady is as a typical module instead of as a dojo plugin with the "!" convention as per the dojo documentation http://dojotoolkit.org/documentation/tutorials/1.8/modules/ see using plugins.
To use domReady correctly it should look like this..
require(["dojo/domReady!"], function(){
// will not run until DOM is finished loading
});
http://dojotoolkit.org/reference-guide/1.10/dojo/domReady.html outlines when and how to use dojo/domReady! vs dojo/ready
The Code
You can find the JSFiddle in question at: http://jsfiddle.net/SeanKilleen/A3QtJ/
Background / What I'm Trying to Accomplish
I'd like a Feedback button to place on our web site, likely in the Site.Master file (it's an asp.net web site)
When the feedback link is clicked, I'd like to show a modal dialog
I'd like to bind the link and the elements inside of the modal to a specific knockout viewmodel
I'd like to properly namespace it so that it doesn't interfere with any other scripts that might come up on other pages
I'd like to apply the Knockout bindings only to this portion of the code, because other subsequent pages, etc. might also have bindings.
To do this, I have the following main toolset: Knockout, jQuery, and jQueryUI (jQueryUI isn't my particular choice but that ship has sailed).
The Problem
In the JSFiddle link, the following code currently works:
$(document).ready(function () {
vm = new FeedbackNamespace.ViewModel();
ko.applyBindings(vm);
});
However, when I change ko.applyBindings(vm) to:
ko.applyBindings(vm, document.getElementById('FeedbackArea'));
The link part of the binding (that is mound to a viewmodel function to show the dialog) still works. However, none of the bindings inside the modal dialog still work.
Question(s)
How can I properly apply the viewModel to only a section of the site in this case?
Is this method of doing things still going to cause problems with child pages that might load their own knockout viewmodels and apply them?
Are there other examples of this sort of thing being done? I've been looking but unable to find them.
Thanks in advance for any help you can give!
The problem is here:
self.Start();
this sets up the modal, removing it from the FeedbackArea div. This happens in the process of creating the viewmodel, such that when this newly created vm is actually applied to the div a moment later, that modal is now gone, which is why nothing inside of it responds like it does when you apply the VM to the entire page.
I would make sure that Start method is called AFTER you apply bindings.
LIKE THIS
I am trying to use Backbone.Paginator.js to run more than one app (multiple instances of paginator) on the same page.
I created a test page. (Navigate to backbone.paginator/examples/netflix-infinite-paging).
I left the code as is for app.js and create app2.js, which is a clone of app.js but all the javascript code is located in one file and the app has been renamed to app2.
Two instances work on first load of the page but subsequent request/refreshes only load app2.js's data.
Is it possible to run multiple instances on the same page?
I am interested in using an auto-paging (infinite/endless scroll) so
I tried to use Paul Irish's jQuery Infinite Scroll plugin but
I am unable to get it to work.
I am initiating the plugin to run on document ready (which does not
work, as expected), but also running the code in the app2's
ResultView, which does not work as well.
Any ideas on how to get an auto-paging infinite scroll solution?
I ran into https://github.com/joneath/infiniScroll.js but I am not
sure how to integrate it with Backbone.Paginator.js.
I am still learning and any help would be greatly appreciated! :)
UPDATE: After further testing across different browsers, it seems like the problem might be to caching issue/differences. For example, in Safari, it works sometimes (randomly) when refreshing the page. I am not sure how to debug that. Any ideas?
Questions:
1- Are you including the jQuery Javascript framework dependencies as well in your codebase?
2- I have downloaded the zip file, ran it on Xammp locally and it appears to be a downloaded demo not a test page, can you please confirm which page is your test page from the compressed file attached to your question?
3- Can you create a mockup (in case that there is some server side code happening) in jsfiddle?
4- The link provided for the Infinite scroll jquery plugin is broken, it should be: https://github.com/joneath/infiniScroll.js
If you want to make the jQuery paginator plugin to be independent, you might want to trigger it considering the container element as well
....
From the suggested link, I think that we should experiment with this. Else you might want to create a cookie or something for the browser to remember the changes to the plugin on multiple instances.. Here are some thoughts?
1#
Backbone.InfiniScroll(collection, **options**)
Instantiate a new InfiniScroll object after your Backbone view has been rendered.
myView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "render");
this.render();
this.infiniScroll = new Backbone.InfiniScroll(this.collection, {success: this.appendRender});
}
)};
2# At a glance from the Options menu
target: $(window),
Perhaps we should try:
$(body).find('#container1'),
-or-
$(window).children('div').hasClass('container'),
Just some ideas, haven't experimented it myself-
3# You might want to make (1) to be a javacript function and trigger it based on a class or on it's id for initializing the scroll over a desired container.
That's all the ideas I could come up by taking a look real quick, but feel free to reply if it helps out or at least gives some direction.
4# Another thought is that myView can be a variable with an id of a timestamp in Javascript, that way you can ensure uniqueness and since you are calling new then you could have several instances of the plugin running for your view.
I have seen many questions raised around PartialViews and Javascript: the problem is a PartialView that requires Javascript, e.g. a view that renders a jqGrid:
The partial View needs a <div id="myGrid"></div>
and then some script:
<script>
$(document).ready(function(){
$('#myGrid').jqGrid( { // config params go here
});
}
</script>
The issue is how to include the PartialView without littering the page with inline tags and multiple $(document).ready tags.
We would also like to club the results from multiple RenderPartial calls into a single document.Ready() call.
And lastly we have the issue of the Javascript library files such as JQuery and JQGrid.js which should ideally be included at the bottom of the page (right before the $.ready block) and ideally only included when the appropriate PartialViews are used on the page.
In scouring the WWW it does not appear that anyone has solved this issue. A potential way might be to implement a custom View Engine. I was wondering if anyone had any alternative suggestions I may have missed?
This is a good question and it is something my team struggled with when JQuery was first released. One colleague wrote a page base class that combined all of the document ready calls into one, but it was a complete waste of time and our client's money.
There is no need to combine the $(document).ready() calls into one as they will all be called, one after the other in the order that they appear on the page. this is due to the multi-cast delegate nature of the method and it won't have a significant affect on performance. You might find your page slightly more maintainable, but maintainability is seldom an issue with jQuery as it has such concise syntax.
Could you expand on the reasons for wanting to combine them? I find a lot of developers are perfectionists and want their markup to be absolutely perfect. Rather, I find that when it is good enough for the client, when it performs adequately and displays properly, then my time is better spent delivering the next requirement. I have wasted a lot of time in the past formatting HTML that no-one will ever look at.
Any script that you want to appear at the bottom of the page should go inside the ClientScriptManager.RegisterStartupScript Method as it renders at the bottom of the page.
http://msdn.microsoft.com/en-us/library/z9h4dk8y.aspx
Edit Just noticed that your question was specific to ASP.NET MVC. My answer is more of an ASP.NET answer but in terms of the rendered html, most of my comments are still relevant. Multiple document.ready functions are not a problem.
The standard jQuery approach is to write a single script that will add behaviour to multiple elements. So, add a class to the divs that you want to contain a grid and call a function on each one:
<script language="text/javascript">
$(document).ready(function(){
$('.myGridClass').each(function(){
$(this).jqGrid( {
// config params can be determined from
//attributes added to the div element
var url = $(this).attr("data-url");
});
});
}
</script>
You only need to add this script once on your page and in your partial views you just have:
<div class="myGridClass" data-url="http://whatever-url-to-be-used"></div>
Notice the data-url attribute. This is HTML5 syntax, which will fail HTML 4 validation. It will still work in HTML 4 browsers. It only matters if you have to run your pages through html validators. And I can see you already know about HTML5
Not pretty but as regards your last point can you not send the appropriate tags as a ViewData dictionary in the action that returns the partial?