With the lack of the script registrar, the MVC extensions appear to dump thier javascript in the middle of the body, immediately after the element being modified. The outputted javascript is wrapped in an auto-running function so that it executes immediately. Apart from dumping js into the middle of the page, which is by popular opinion considered a dubious practice, this causes problems with other javascript libraries that may then go in afterwards and manipulate the DOM. If such a library snips out the parent of an element in question which happens to contain the associated script tags and reinserts it, all of the javascript snippets run again, causing the Kendo controls to break (because they have been initialised twice) and everything to fail. If the Kendo scripts were at the end of the body this wouldn't be an issue.
Why isn't all javascript rendered at the end of the body? How can this be resolved?
29 Answers, 1 is accepted
Currently Kendo UI widgets output their JavaScript in a $(function() {} ) block. This means that it will execute when the DOM ready event is raised. If you want to modify the DOM before that you can attach your handler before the declaration of the Kendo widgets:
<
script
>
$(function() {
// modify dom
});
</
script
>
@(Html.Kendo().DatePicker())
There is no way right now to prevent the widgets from rendering their initialization JavaScript as without it nothing will work.
Regards, Atanas Korchev
the Telerik team
Say I insert a dropdown using DropDownFor into a div. This is the rendered HTML output:
<
div
class
=
"editor-field"
>
<
span
class
=
"k-widget k-dropdown k-header"
tabindex
=
"0"
style
=
""
unselectable
=
"on"
>
<
script
>
jQuery(function(){jQuery("#MyField_Something").kendoDropDownList();});
</
script
>
<
span
class
=
"k-invalid-msg"
data-for
=
"MyField.Something"
></
span
>
</
div
>
If I then remove this div from the DOM and reinsert it, the Kendo handler will get attached twice, once when the document first loads and a second when I reinsert it. This causes problems - in the case of the dropdown list, it disappears.
Can you exclude the <script> elements when moving the DOM nodes? If they have been executed already you can just remove them before performing the move:
$(
"<some selector>"
).find(
"script"
).remove().end().appendTo($(
"<some other selector"
));
What is the code used to move the DOM elements? Regards,
Atanas Korchev
the Telerik team
My concern with this is that it seems hacky rather than a real fix. It's also a pain to do if the DOM manipulation is performed by a third party library that's been minified - I can't edit their source and am left trying to hook in my event at the correct point in time to intercept the move. On top of that, in the interests of clean HTML/script I'm sure I'm not the only dev who would prefer to see this script down at the bottom of the rendered body rather than splattered throughtout the DOM. Are there any plans to have the scripts render in a similar fashion to the old extensions?
We currently don't have plans to revive the ScriptRegistrar. We could probably however investigate if we can use Razor sections to output the script in a user specified location. I am not sure if this is even possible but is worth checking.
Regards,Atanas Korchev
the Telerik team
Thanks, I certainly think this is worth investigating. I'm not too bothered about the script registrar being revived but I do think it would be nice to be able to instruct Kendo to render it's scripts at the bottom of the body. One of the nice things about the old extensions was that rendered HTML was quite clean and I think that should be a key selling point of Kendo as well.
Asthetics aside, I guess it's questionable whether this can be called a 'bug', but I do think the DOM manipulation issue will cause other users similar problems down the road. This is the first form I've tried to build with Kendo (albeit a rather complex one with an integrated jQuery wizard) and while it works when I use the old Telerik extensions, it doesn't with Kendo. The behaviour should at least be equivalent to allow easy transition for existing users.
For me I feel this is a deal breaker when it comes to adoption of the new toolset. Is there any way that I can track progress on this issue?
Do you mind sharing with us how you achieved rendering of the script at the bottom of your page?
All the best,Atanas Korchev
the Telerik team
Hi Atanas,
Sure. I know we mentioned Razor sections earlier but I'm pretty sure that wouldn't work for EditorTemplates. In the end I've gone in a similar direction to the old school ScriptRegistrar and just recreated a lightweight version of that class. It's not a fully blown ScriptRegstrar or anything, it's merely an internal class that exposes a method that allows scripts to be registered (with a key) during processing of a view (I actually already had this class available as I use it to register JavaScript snippets inside reusable PartialViews). The thin HTML helpers I've created use this class internally to add the initialization scripts which stores them in the WebViewPage's context items collection and I have a single line of code at the end of my layout that requests the ScriptRegistrar to render those scripts.
Cheers.
I created a section for rendering my scripts bottom of my jquery reference,
my problem is, the scripts generate by kendo.mvc, render after my reference of jquery,
when I put it jquery on head then "columns.Template" not work.
Is your solution available online anywhere? I'm really keen to get this into our project to avoid the kind of problems you've had.
Many thanks,
Chris
Basically what the code does:
HtmlHelper Extensions
Defer()
RenderDeferredScripts()
Defer gets passed a function that passes the HtmlHelper and expect an object in return.
public static MvcHtmlString Defer<TModel>(this HtmlHelper<TModel> htmlHelper, Func<HtmlHelper<TModel>, object> action)
RenderDeferredScripts should be called after your other scripts has loaded
public static MvcHtmlString RenderDeferredScripts(this HtmlHelper htmlHelper)
The defer method renders the action given the html helper, the result string is then passed through to the helper method ExtractScript
that splits up the html and script. The extracted script/s are then loaded into the HttpContext.Items (persists unlike the ViewBag), with the scripts inserted at position 0 (to keep the ordering top-down so that layout-*layouts-view-partialview scripts are rendered in order).
It's a bit of workaround, ideally the mvc extensions would just render the html and defer the script/s in a similar way as demonstrated here.
Sample usage:
View:
@Html.Defer(html => html.Kendo().DropDownListFor(p => p.SalutationId).BindTo(Model.Salutations).HtmlAttributes(new { @class = "width-100" }))
Layout:
@Scripts.Render("~/public/siteJs")
@Html.RenderDeferredScripts()
Essentially that's what I did, however I noticed problems with this approach when using Kendo editors within a Kendo grid. In that scenario the editor controls broke because of the way that the MVC extensions render the JS and the way that Kendo's client side code works; it literally reinitializes the editors every time you put the grid into edit mode and it expects the scripts to be rendered inline. I never got around this issue and decided it was too much effort to maintain my patch. I instead modified the way my app worked. Disappointing and a rather large oversight on Kendo's part which I can't see being fixed unfortunately.
Thanks for the idea. We have played around with it and seems to came up with a possible improvement for the described scenario. The idea is to have a per widget
setting which will "defer" the rendering of initialization scripts. Similar to the following:
@(Html.Kendo().Grid<
ProductViewModel
>()
.Name("Grid")
.Deferred()
/*..*/
)
@*some other content*@
@Html.Kendo().RenderDeferredScripts()
This setting will not be automatically propagated to the child widgets, thus the Grid editing, for example, should still be functional.
What do you think? Will this be suitable for your scenarios?
All the best,
Rosen
the Telerik team
I was thinking this over and I think I would prefer the following:
- Initialization scripts stay where they are (just after the elements they initialize), hence not being deferred to the end of the page
- Deferring should be the default behavior (or some other way)
ex.
@Html.Kendo().RenderDeferredScripts()
@Html.Kendo().Deferred().DropDownListFor(p => p.SomeId).BindTo(Model.SomeItems)
The only way to get what I want without requiring a major rewrite of the helpers, would be to actually just stub out the $(document).ready function used to initialize the scripts. I went looking and found Sam Saffron (from StackOverflow) has considered the same approach and according to him it's feasible: Edit: The implementation here seems better
Proposed solution
1. With all scripts rendering just before the closing of the html->body tag:
<!-- Place in head : document.ready stub function -->
<
script
>(function(w,d,u){w.readyQ=[];w.bindReadyQ=[];function p(x,y){if(x=="ready"){w.bindReadyQ.push(y);}else{w.readyQ.push(x);}};var a={ready:p,bind:p};w.$=w.jQuery=function(f){if(f===d||f===u){return a}else{p(f)}}})(window,document)</
script
>
<
script
>(function($,d){$.each(readyQ,function(i,f){$(f)});$.each(bindReadyQ,function(i,f){$(d).bind("ready",f)})})(jQuery,document)</
script
>
2. With all scripts rendering in the <head> section
No action required
I believe this proposed solution would let us have our cake and eat it too. Another plus for Kendo, it should be portable to any platform that is being catered for.
To account for developer laziness and to make it easier for those starting out with Kendo, I'm also proposing adding two methods to the ASP.NET MVC Helper (and other helpers) to make this approach more robust.
<!-- Place in head : document.ready stub function -->
@Html.Kendo().Defer().PreInit()
@Html.Kendo().Defer().Init()
Edit: The implementation here seems better
Kind regards,
Schalk
This deferred scripts approach seems to work ok with partial views. I have attached a small sample which demonstrates this use case. Please take a look, maybe I'm missing something.
Regards,
Rosen
the Telerik team
I would still highly recommend that the jQuery document ready helper stub be used instead of the script deferral approach. I have tested the stub method and seems to work fine.
I took a look at the project you attached. Unfortunately I was not able to open the project, but I did browse code via text editor.
Here are some 2 other scenario's to consider with the deferred scripts approach:
- @Html.Kendo().DeferredScripts() gets called more than once (ex. on View and then later on Layout) with the first @Html.Kendo().DeferredScripts() called after the required resources are available (jQuery/Kendo etc.)
- Deferred scripts are never output:
1. Partial View is returned from Action Method with no @Html.Kendo().DeferredScripts() present
2. Scripts are deferred after calling @Html.Kendo().DeferredScripts() (which means the response gets sent without the deferred scripts included)
The HttpContext.Items collection will persist to Http Handlers.
- Assign an arbitrary identifier to the request & add a script reference (ex. ~/deferred.axd?key=) where the deferred scripts should be collected
- Collect all the deferred scripts.
- On a Http Handler EndRequest add the deferred scripts to the cache using the arbitrary identifier.
- The browser will initiate a request to fetch the deferred scripts, whereafter the cache entry can be removed.
Kind regards,
Schalk
Thanks for the feedback.
If I understood correctly the later approach, you should be able to use it with current version of KendoUI for ASP.NET MVC without any modifications of the code.
However, we think that the DeferredScripts approach is still applicable solution in most of the scenarios. It also does not require inclusion of two more scripts on the page, which also is error prone.
Rosen
the Telerik team
You are correct in saying that the document ready stub solution is usable without any modifications of the code.
I thought then that this solution would be the preferred method considering that KendoUI is catering for other platforms - currently ASP.NET MVC and JSP and maybe some others such as Play/Lift,Grails,Django,RoR,PHP etc. later?
I guess the deferred scripts approach could still be useful. Could I then also suggest sharing the pipeline (make it available via html helper Kendo extension) so developers can defer their own scripts?
I honestly can't think of anyone objecting to adding 2 inline scripts so that they can benefit from placing their scripts before the body close of the html page. The only real benefit from deferring script execution is to provide the illusion of improved responsiveness (eg. the page will be usable before all the scripts are executed)
Thanks for the quick responses and good luck on your side.
Kind regards,
Schalk
just wanted to know what the Kendo team have decided to do to solve the problem of scripts rendered in the middle of the body. In my projects, i declare all my scripts at the end of the body and because Kendo MVC wrapper render his scripts in the middle, i have a "jQuery is not define" error.
For now, the only solution i've found is to put the jQuery and the kendo scripts in the head section but i don't want it to be a permanent solution.
thank you for your help!
As I have mentioned, we will implement the DeferredScripts approach for the Q1 2013 version of KendoUI for ASP.NET MVC. Meanwhile, you are free to use all of the approaches described in this thread.
Greetings,
Rosen
the Telerik team
I have just start to try KendoUI MVC wrapper.
I'm using deferred scripts and it works as expected.
I have some questions about:
- is it possible to bundle all the controls initialization in one single jQuery initialization function instead of a call for each of the deferred controls?
- if not, there is plans to implement such thing?
- Eventually is a contribution accepted?
I'm afraid that this is currently not possible. However, we may consider implementing such option for future version of the KendoUI for ASP.NET MVC base on the user interest. Therefore, you may consider posting your idea in our user voice.
Regards,Rosen
the Telerik team
Lorenzo
Feel free to share your code with us here if you like.
Regards,Rosen
the Telerik team
In the end the solution was to render the script block just inside one of the functions that will be called on require calback function, however I observed that DeferredScripts will output also the <script> tag which may be normal but it is a limitation in my case. I had to use a temporary string to replace all <script> tags with "" to be able to have proper script in my require callback function.
@{
string defferedScript = Html.Kendo().DeferredScripts().ToString();
defferedScript = defferedScript.Replace("<script>","");
defferedScript = defferedScript.Replace("</script>","");
}
<script type="text/javascript">
require([module], function (m) {
@Html.Raw(defferedScript);
});
</script>
Would be excelent to have an option (parameter) in DeferredScripts which will enable us to let Kendo know if we want the <script> tags or not, in my opinion would make sense to have this little option to skip the penalty of discarding the tags with replace.
Thank you
Vasile
Thanks for the suggestion. We have added such option to DeferredScripts method. It will be available with the Q1 2013 release of KendoUI, which is scheduled for the second half of March.
All the best,Rosen
the Telerik team