In a single page application, how to use Knockout.js effectively to achieve MVVM. Is there a way to create views (Html files) and viewmodels (javascript files) separately? That can be referred in another HTML master file.
I tried iframe, but it allows user to navigate to individual views. Is there any other way?
Thanks in advance.
Yes, I think this is possible. If you create each view as a template in a separate html file and then load and append the appropriate template to the documents body when you instantiate you viewmodel.
in pseudo-code you'd get something like:
<!-- your page -->
<div id="bindModelHere">
<!-- ko template: loadedTemplate -->
<!-- /ko -->
</div>
<script type="text/javascript">
$(document).ready(function () {
var viewModel = new yourViewModel();
ko.applyBindings(viewModel, document.getElementById('bindModelHere'));
});
</script>
//your javascript
function yourViewModel (){
this.loadedTemplate = ko.observable();
//Load template from server with a get and append to document body
//when loaded and appended do: loadedTemplate("idForTemplate");
}
<!-- the template/view in a separate file-->
<script type="text/html" id="idForTemplate">
<!-- your html -->
</script>
this way you could load your views/templates using ajax and just load the view you'd need.
Related
I want to exract some js file and call it only in one view, separetely from my bundles which is called from _LandingLayout.cshtml template page.
In my BundleConfig.cs I have
bundles.Add(new ScriptBundle("~/scripts/app/index").Include(
"~/Scripts/App/home.js",
"~/Scripts/App/location.js"
));
and my view like this:
#{
ViewBag.Title = "Home Page";
Layout = "~/Views/Shared/_LandingLayout.cshtml";
}
<!-- Main Content -->
...some html markup
<!-- End of Main Content -->
#Scripts.Render("~/scripts/app/index")
every css and js file from _LandingLayout.cshtml is shows correctly, if I put "~/scripts/app/index" into _LandingLayout.cshtml, it shows correctly.
But if I want to render it separately from my _LandingLayout.cshtml it enters into js file home.js and yellows whole the file like a debugger (I didn't put any debugger into my home.js) and when I enter F10, I get an error:
0x800a1391 - JavaScript runtime error: '$' is undefined
I have searched for this error and I find this
But, I have referenced jQuery lib.
Only that cross my mind, that maybe engine renders first my line of code:
#Scripts.Render("~/scripts/app/index")
and later on renders all bundles from _LandingLayout.cshtml page?
you need to add you new section in _LandingLayout.cshtml view after all js files referenced inside it.
for example we will add section named "ExtraJs" after jquery reference as following:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
#RenderSection("ExtraJs", false)
then inside your view you can reference this section as following
#{
ViewBag.Title = "Home Page";
Layout = "~/Views/Shared/_LandingLayout.cshtml";
}
<!-- Main Content -->
...some html markup
<!-- End of Main Content -->
#section ExtraJs
{
#Scripts.Render("~/scripts/app/index")
}
I need to figure out how to display JSON data from an ASPHX file into an
ASPX file without using C#. My theory is that I am not able to run the JavaScript because of the order of loading. Using web forms, how can I use a handlebars template inside of an ASP control content placeholder? The below code does not return any HTML to the page.
============= ASP CODE / HTML =============
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="unprocessed.aspx.cs" MasterPageFile="~/Default.Master" Inherits="SurplusApp.unprocessed" %>
<asp:content ContentPlaceHolderID="mainContent" runat="server">
<script id="entry-template" type="text/x-handlebars-template">
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
</script>
</asp:content>
=============== JS CODE =================
var source = $("#entry-template").html();
var template = Handlebars.compile(source);
var context = { title: "My New Post", body: "This is my first post!" };
var html = template(context);
Update #1:
What I have discovered is that when using web form pages the content placeholder tag does not support the script tag <script id="entry-template" type="text/x-handlebars-template"> however, you can add the template code in your js file and render the template in your web dev console. An example of this can be found on the handlebars github page: https://github.com/wycats/handlebars.js/
Add a MIME type to your web.config file because a web forms page ASPX file with <asp:content></asp:content> does not support adding <script id="entry-template" type="text/x-handlebars-template">
Create a Handlebars template file.
Create a div with a unique id which we will use later to insert our Handbars rendered template.
On the bottom of your ASPX page add a script tag and add two AJAX handlers, one to return the data from a JSON feed and another to return a handlebars template. In our second AJAX call, this is where we will append our newly created HTML from Handlebars.
var responseObject;
$.get('jsonFeed.ashx', function(response){ responseObject = response });
$.get('handlebarsTemplate.hbs', function(response){
var template = Handlebars.compile(response);
var handlebarTemplateInsert = template(responseObject);
$('#element').append(handlebarTemplateInsert);
});
For some context, the DOM Hierarchy:
Layout.cshtml
> View
> Partial View
The Layout file contains:
<head>
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryui")
</head>
<body>
<div>
#RenderBody()
</div>
#RenderSection("scripts", required: false)
</body>
The View contains a form. After submitting the form, an AJAX call returns the partial view which is inserted into the View using $('selector').html(PartialViewResult).
The Partial View contains:
// #Scripts.Render("~/bundles/jquery") // [†]
#using(Ajax.BeginForm(...)
{
// MinRate has the appropriate "lessthanproperty" data-val HTML properties
#Html.EditorFor(x => x.MinRate)
// MaxRate has the appropriate "greaterthanproperty" data-val HTML properties
#Html.EditorFor(x => x.MaxRate)
#Html.ValidationSummary()
<button type="submit">Submit</button>
}
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/MapRates")
<script>
$(document).ready(function () {
console.log("CSHTML, ON READY");
});
(function() {
console.log("CSHTML, IIFE");
$.validator.addMethod("lessthanproperty", function (value, element, params) {
return Number(value) < Number($(params).val());
});
$.validator.addMethod("greaterthanproperty", function (value, element, params) {
return Number(value) > Number($(params).val());
});
})();
</script>
MapRates Javascript file contains:
$(document).ready(function () {
console.log("JS, ON READY");
});
(function () {
console.log("JS, IIFE")
$.validator.unobtrusive.adapters.add("lessthanproperty", ["comparisonpropertyname"], function (options) {
options.rules["lessthanproperty"] = "#" + options.params.comparisonpropertyname;
options.messages["lessthanproperty"] = options.message;
});
$.validator.unobtrusive.adapters.add("greaterthanproperty", ["comparisonpropertyname"], function (options) {
options.rules["greaterthanproperty"] = "#" + options.params.comparisonpropertyname;
options.messages["greaterthanproperty"] = options.message;
});
})();
Now...from what I can gather, the above sourcecode should work. When the user interacts with the MinRate or MaxRate fields, client-side validation should cause the ValidationSummary to be updated according to any validation errors encountered. † However, the above does not work unless I uncomment the jquery library reference, #Scripts.Render("~/bundles/jquery") at the top of the Partial View, above the Ajax.BeginForm line.
But the jquery script is already included in Layout making this the second time it is loaded and so naturally it breaks some things in another, previously unmentioned partial view. For this reason and for good coding practice, I need to get this validation working without the crutch of referencing the jquery library a second time.
When the unobtrusive validation works, with jquery referenced, the printout statements appear as:
JS, IIFE
CSHTML, IIFE
JS, ON READY
CSHTML, ON READY
When the unobtrusive validation breaks, without jquery referenced, the printout statements appear as:
JS, ON READY
JS, IIFE
CSHTML, ON READY
CSHTML, IIFE
Including the jquery library reference causes the script contents to be loaded in the correct order. Without the library, the validation adapters and methods are not incorporated until after the document is ready which apparently makes the unobtrusive js validation non-functional.
How can I get the validation components to load in the correct order and be functional on the page? Also, can anyone explain why including the jquery reference on the view causes it to work at all? Many thanks in advance for your help!
edit: It occurs to me that it may be pertinent to say that the Partial View is a bootstrap modal. I'm not sure if its being a modal with visibility triggered by a data-toggle could cause any odd behavior with validation binders or not.
However, the above does not work UNLESS I uncomment the jquery library reference, #Scripts.Render("~/bundles/jquery") at the top of the Partial View,
You shouldn't store scripts in partial view's,
Place all your script and script references in Your main view.(references on top) at #section scripts {}
after placing #RenderSection("scripts", required: false) in Your _Layout,which will insert a section named "scripts" found in the view.
Please note that the #Scripts.Render("~/bundles/jquery") in _Layout must be included after #RenderBody() method to be seen on your views, like so:
<!DOCTYPE html>
<html>
<head>
</head>
<body class="body">
<div class="container-fluid">
#RenderBody()
<footer class="footer">
<p>© #DateTime.Now.Year company name</p>
</footer>
</div>
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
</body>
</html>
and Your rendered view
#model someViewModel
#{
ViewBag.Title = "some title";
}
<div class="row">
//some chtml
</div>
#section Scripts {
<script>your scripts</script>
}
Scripts should not be in partial views. Remove all duplicates and include them only in the main view or the layout.
Your issue is that your dynamically loading a form (the partial) after the page has initially be rendered (and therefore after the the $.validator has parsed the form). When loading dynamic content, you need to re-parse the validator in the ajax success callback, for example
$.ajax({
....
success: function(PartialViewResult) {
$('selector').html(PartialViewResult); // append to the DOM
var form = $('form'); // better to give the form an id attribute for selection
// re-parse the validator
form.data('validator', null);
$.validator.unobtrusive.parse(form);
}
});
This question is related to
Django: Best Way to Add Javascript to Custom Widgets
But is not the same.
The original question asks how to add supporting javascript to a custom django widget, and the answer is to use forms.Media, but that solution does not work for me. My example is this:
The widget, when rendered in a form, creates a line which looks like (toy example) this:
<div id="some-generated-id">Text here</div>
Now, what I also want to add to the output is another line looking like this:
<script>
$('#some-generated-id').datetimepicker(some-generated-options)
</script>
The initial idea is that when the widget is rendered, both the div and script get rendered, but that does not work. The problem is that the structure of the html document looks like:
-body
- my widget
- my widget's javascript
-script
-calls to static files (jQuery, datetimepicker,...)
At the time the widget's javascript code is loaded in the browser, jQuery and datetimepicker js files have not yet been loaded (they load at the end of the document).
I cannot do this using Media, since the options and id I generate are vital to the function. What is the best way to solve this?
From the docs:
The order in which assets are inserted into the DOM is often important. For example, you may have a script that depends on jQuery. Therefore, combining Media objects attempts to preserve the relative order in which assets are defined in each Media class.
Consider this example:
class FooWidget(forms.TextInput):
class Media:
js = ('foo.js',)
class BarWidget(forms.TextInput):
class Media:
js = ('bar.js',)
class SomeForm(forms.Form):
field1 = forms.CharField(widget=BarWidget)
field2 = forms.CharField(widget=FooWidget)
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
Now when you call form.media, the scripts will render like this:
<script type="text/javascript" src="/static/bar.js"></script>
<script type="text/javascript" src="/static/foo.js"></script>
Why does bar.js render before foo.js? Because django renders them based on the order they were called on in the form, not the order that the classes were defined in. If you want to change the order in this example, simply swap the position field1 and field2 in SomeForm.
How does this help you with jQuery? You can render your jQuery CDN script via your custom widget:
class FooWidget(forms.TextInput):
class Media:
js = ('https://code.jquery.com/jquery-3.4.1.js', 'foo.js',)
class BarWidget(forms.TextInput):
class Media:
js = ('https://code.jquery.com/jquery-3.4.1.js', 'bar.js',)
Now your form.media will look like this:
<script src="https://code.jquery.com/jquery-3.4.1.js"></script>
<script type="text/javascript" src="/static/bar.js"></script>
<script type="text/javascript" src="/static/foo.js"></script>
Notice how /static/ wasn't appended to the jQuery CDN? This is because the .media attribute checks whether the given filepaths contain http or https, and only appends your STATIC_URL setting to filepaths that are relative.
Also note that duplicate file names are automatically removed, so I would say it's good practice to include a https://code.jquery.com/jquery-3.4.1.js at the beginning of every widget that requires it. That way, no matter what order you render them in, the jQuery script will always appear before files that need it.
On a side note, I would be careful when including numbers in your filenames. As Django 2.2 there appears to be a bug when trying to order the scripts.
For example:
class FooWidget(forms.TextInput):
class Media:
js = ('foo1.js', 'foo2.js',)
class BarWidget(forms.TextInput):
class Media:
js = ('bar1.js', 'bar13.js',)
class SomeForm(forms.Form):
field1 = forms.CharField(widget=BarWidget)
field2 = forms.CharField(widget=FooWidget)
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
Will look like:
<script type="text/javascript" src="/static/bar1.js"></script>
<script type="text/javascript" src="/static/foo1.js"></script>
<script type="text/javascript" src="/static/bar13.js"></script>
<script type="text/javascript" src="/static/foo2.js"></script>
I've tried various combinations of names containing numbers, and I can't follow the logic, so I assume this is a bug.
Since the JavaScript is an inline script, you will need to use a the native DOMContentLoaded event to wait for the jQuery to load.
<script>
window.addEventListener('DOMContentLoaded', function() {
(function($) {
$('#some-generated-id').datetimepicker(some-generated-options);
})(jQuery);
});
</script>
Alternately, if you can put your code into an external script file, you can use the defer attribute of the script tag.
<script src="myfile.js" defer="defer"></script>
See the MDN.
You want to execute some plugin script on added div. You need to add class to your element and associate a custom event to class. Which will execute your desire function or script.
To associate custom event to dynamically added node, please refer below code.
<!DOCTYPE html>
<html>
<head>
<title>adding javascript to custom widgets</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body>
<section class="team">
<div class="container">
<div class="row">
<div class="container maxwidth">
<button data-tab="tab1" class="active">AA<span></span></button>
</div>
<div class="maincontent"></div>
</div>
</div>
</section>
<script>
$(function(){
$(".active").click(function(){
$(".maincontent").append("<div class='scroll'><h2>Hello</h2><div style='background:red; height:500px;'></div></div>")
$(".maincontent").find(".scroll").trigger('dynload');
});
$('.container').on('dynload', '.scroll', function(){
console.log("Append event fired");
// Additinal Script resource you want to load for plugin
$.getScript("Custom_Scrollbar.min.js") //script URL with abosolute path
.done(function() {
// Script loaded successfully calling of function
$(".scroll").mCustomScrollbar({
});
})
.fail(function() {
// Give you error when script not loaded in browser
console.log('Script file not loaded');
})
})
})
</script>
</body>
</html>
Hope this will help!
You should init JS as:
<script type="text/javascript">
$( document ).ready(function() {
var some-generated-options = {};
$('#some-generated-id').datetimepicker(some-generated-options);
});
</script>
Like you I did own custom widget that look as:
class DaysInput(TextInput):
def render(self, name, value, attrs=None):
result = super(DaysInput, self).render(name, value, attrs)
return u"""
%s
<script type="text/javascript">
$( document ).ready(function() {
$('a.id_days').click(function(e){
e.preventDefault();
$('input#id_days').val($(e.currentTarget).attr('attr-value'));
});
});
</script>
<a href="#" class="id_days" attr-value='1'>1d</a>
<a href="#" class="id_days" attr-value='2'>2d</a>
<a href="#" class="id_days" attr-value='3'>3d</a>
""" % result
class TestForm(forms.ModelForm):
days = forms.IntegerField(widget=DaysInput())
I have a fully functional html file right now that has a lot of content, and I'd like to increase readability by storing the table in a separate file. The table data is about 3,000 lines of html. The current structure is:
HTML:
<body>
<!-- Lots of content -->
<table>
<!-- Lots of data -->
</table>
<!-- Other content -->
<script>
<!-- Set up as table as datatable (jQuery plug-in) -->
<!-- Add dynamic formatting to table -->
</script>
</body>
How can I replace the <table>...</table> with just a call to another html file that contains all of the data, so that I can still do all of the javascript stuff to the table?
Using jQuery, you can do this with a GET command and then appending it do a div in your body where you want the table to end up.
The code for importing data would look like this:
$.ajax(
{url:"tabledata.html",
dataType:"text"}).done(
function(data){
$('#id_of_div_to_append_to').html(data);
});
Also, you'd need to add a div to append the data in your HTML:
<body>
<!-- Lots of content -->
<div id = "id_of_div_to_append_to"></div>
<!-- Other content -->
<script>
<!-- Set up as table as datatable (jQuery plug-in) -->
<!-- Add dynamic formatting to table -->
</script>
</body>
This is a very complex solution to my question, but if you have the time to learn Angular, I'd recommend it:
https://www.codeschool.com/courses/shaping-up-with-angular-js
Angular allowed me to make my html much more readable and dynamic.