Adding dojo widget inside custom widget - javascript

I am making a small dojo widget, basically extending the dailog widget and want to add simple widgets like a text input a few labels etc. How do i go about this? I am following a tutorial,
Dojo how to make a widget
Please help me out.
Thanks

First. I'am not good at english, but will do at my best.
This is the path of my widget.
Here. The important code in the js file that must declare.
dojo.provide("gissoft.dijit.widgetOam");
dojo.require("dojo.parser");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare("gissoft.dijit.widgetOam", [dijit._Widget, dijit._Templated], {
widgetsInTemplate: true,
basePath: dojo.moduleUrl("gissoft.dijit"),
templatePath: dojo.moduleUrl("gissoft.dijit", "templates/widgetOam.html"),
constructor: function () {
},
postMixInProperties: function () {
},
postCreate: function () {
},
startup: function () {
}
});
And in file widgetOam.html(templatePath)
<div> <!-- do not add tag <html> , <head> , <body> but must have <div> first -->
Hello World.
</div>
And this is how to call widget from my Default.aspx
You must add this before you call the dojo library
<script>
djConfig = {
parseOnLoad: true,
baseUrl: './',
modulePaths: { 'gissoft.dijit': 'js/gissoft/dijit' }
};
</script>
And in the body
<body class="tundra">
<form id="form1" runat="server">
<div>
<div data-dojo-type="gissoft.dijit.widgetOam"></div>
</div>
</form>
</body>

If I understood correctly, you are asking about how to include another widgets within your custom widget. If that's the case, then we have to modify OammieR's answer a bit, as it's not complete in regards to your question.
To include other widgets within your custom widget, you should include them in your widget declaration:
dojo.provide("gissoft.dijit.widgetOam");
dojo.require("dijit.form.Button"); //<- this the standard widget you want to have in your widget
dojo.require("dojo.parser");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare("gissoft.dijit.widgetOam", [dijit._Widget, dijit._Templated], {
widgetsInTemplate: true,
basePath: dojo.moduleUrl("gissoft.dijit"),
templatePath: dojo.moduleUrl("gissoft.dijit", "templates/widgetOam.html"),
particularly important is the "widgetsInTemplate: true" part which tells the parser to expect the widgets inside the widget.
Then you just include the HTML markup for the particular widget you'd like to include in your widget's template.
<div> <!-- do not add tag <html> , <head> , <body> but must have <div> first -->
<button data-dojo-type="dijit.form.Button" type="button" data-dojo-attach-point="_innerWidget" data-dojo-attach-event="ondijitclick:_onClick">Yo!</button>
</div>
dojoAttachPoint is useful so you can get a reference to this widget straightaway in your widget's implementation without getting a reference via dijit.byId('').
Hope this helps.

Related

Including a JS library in the root view can't be used by partial view using ASP.NET

When I include a JS library in my partial view, subsequent button clicks don't have access to the included library.
If I include the library in the root view, any reference to the library in the partial views fails.
I have been having a lot of trouble using a JavaScript library in my htmlPartial view. I have a partial view that uses the JSTree library, which looks something like this:
#model modelData
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/jstree.min.js"></script>
<div id="my-tree">
</div>
<button onclick="clickButton()"></button>
<script type="text/javascript">
function renderTree() {
$('#my-tree').jstree({
"core": {
"data": data,
"themes": {
"dots": false,
"icons": false
}
},
"checkbox": {
"three_state": false,
"keep_selected_style": true
},
"plugins": ["wholerow", "checkbox"]
});
}
function clickButton() {
console.log($("#my-tree").jstree("get_selected").text());
}
</script>
When I run it the tree renders fine, but when I click the button (to get the selected nodes) it fails with the error:
TypeError: $(...).jstree is not a function
The fix, I have found to this, is to include the jstree.min.js include in the root view as well as the partial.
Here's a breakdown of what I have tried:
include in the partial view only (fail)
include in the root view only (fail)
include in both (works)
So my question is, why does this behavior happen? Here was my thinking:
The button was in the same scope as the library include, so it
should have access to it
The library was included in the root so all
views should have access to it
EDIT
Here is the root cshtml file
#section Scripts {
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/jstree.min.js"></script>
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css">
<div class="tree">
<div>
#(await Html.PartialAsync("MyPartialModule", Model))
</div>
</div>

Cannot reference jQuery .js file in HTML

I can create a jQuery function within my HTML no problem:
<body>
<input type="text" id="mainInput" />
<button class="btn" id="mainButton"></button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
$("#mainButton").click(function () {
$("#mainInput").hide("slow");
});
});
</script>
</body>
This does exactly what I intend it to do.
I read it was a good idea to create a separate script to contain the actual function, so I put the script in its own file...
<body>
<input type="text" id="mainInput" />
<button class="btn" id="mainButton"></button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="~/js/jQueryPractice.js"></script>
</body>
Here is the .js file with the jQuery
$(document).ready(function () {
$("#mainButton").click(function () {
$("#mainInput").hide("slow");
});
});
As soon as I do this, the functionality no longer works.
From everything I have researched, this should be working as intended; I have read multiple tutorials that use this method. I must be missing something simple...
This path:
src="~/js/jQueryPractice.js"
Uses ~, which doesn't really mean anything to a browser. What URL do you expect that to refer to? Should it be the root of the server?:
src="/js/jQueryPractice.js"
Relative to the page?:
src="./js/jQueryPractice.js"
Something else?
Whatever the URL is for the script, relative or absolute, that's what needs to be used in the src attribute.
Here is what got it to work.
My project has a 'wwwroot' directory. It was included when generating a new ASP.NET Core application through Visual Studio.
There is a js folder inside of this. When I put my script in there, the browser started finding it successfully.
This was the HTML:
<script src="~/js/jQueryPractice.js"></script>
The '~' seems to refer to 'wwwroot'. The script is now referencing and behaving as intended.

Dojo AMD not loading custom module

I'm trying to make a web application using the ArcGIS API, Dojo, and Flask. I want to start by making a "file uploads" dialog, which I am trying to define as its own module using the Dojo 1.7 AMD convention (i.e. "define").
Here is my file structure:
\static
home.js
fileUpload.js
\templates
home.html
main.py
Here is the code for the dialog (copied from one of the Dojo Tutorials). I am basically trying to put all dialog related function (i.e. show and hide) in one module:
define([
"dijit/registry",
"dijit/Dialog",
"dijit/form/Button",
"dojo/ready",
"dojo/domReady!"
], function (registry) {
console.log("HELLO WORLD");
return {
// Show the dialog
showDialog: function() {
registry.byId("uploads").show();
},
// Hide the dialog
hideDialog: function() {
registry.byId("uploads").hide();
}
}
});
At the end of "home.js" I try to create and instance of the dialog module:
var fu = new fileUpload();
Then in my "home.html" file, I define the actual dialog and try to use the "fu" object's variables as event handlers for closing and opening the dialog:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>morPOP</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link rel="stylesheet" href="https://js.arcgis.com/4.5/esri/css/main.css">
<link rel="stylesheet" href="../static/css/home.css">
<script src="https://js.arcgis.com/4.5/"></script>
<script src="../static/js/home.js"></script>
</head>
<body>
<!-- Map -->
<div id="viewDiv"></div>
<!-- Upload Button -->
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
<button type="button" id="uploadbtn" class="btn btn-primary" onclick="fu.showDialog()">Upload</button>
</div>
<!-- Upload Dialog -->
<div class ="dijitHidden">
<div id="uploads" data-dojo-type="dijit/Dialog" data-dojo-props="title:'Upload Files'">
<p>The following files must be uploaded to run a simulation. File names must match those listed below.</p>
<p>Acceptable file extensions: .txt or .csv</p>
<ul>
<li>Geographic data</li>
<ul>
<li>Age_Dissemination</li>
</ul>
<li> Probability Data </li>
<ul>
<li>ageContactDuration_hospital_nurse</li>
<li>ageContactDuration_hospitalPatient</li>
<li>ageContactNumber_hospital</li>
</ul>
<li> ??? </li>
<ul>
<li>Census_Division_Mapping</li>
</ul>
</ul>
<button onclick="fu.hideDialog();">Finish</button>
</div>
</div>
</body>
</html>
The error that I get in the google Chrome developer console is the following:
Uncaught TypeError: Cannot read property 'on' of undefined
at new g (init.js:56)
at home.js:51
at Q (init.js:18)
at init.js:18
at A (init.js:18)
at ea (init.js:18)
at d (init.js:20)
at HTMLScriptElement.<anonymous> (init.js:23)
I'm not sure what "on" property the error is referring to. Does anyone have any ideas? Why can't I declare an instance of my module?
** EDIT ***
I've changed my home.js file to "require" fileUpload.js, but I am now getting the following error when I try to click the "submit" button:
(index):24 Uncaught ReferenceError: fu is not defined
at HTMLButtonElement.onclick ((index):24)
Please see the following plunkr for my updated home.js file: https://plnkr.co/edit/9dFVHsFOCji1aE0ZeLRQ?p=preview
When using AMD you manage your dependencies by defining things as you have done with define() but client of module must import it using require() function, see docs, meanwhile you try to instantiate required module via new which is not correct.
To use some module in DOM handler you need extra wrapper e.g. your HTML would make onclick="whenClicked()" if you have this function in scope:
function whenClicked() {
require(['fileUpload'], function(fu) {
fu.showDialog();
});
}
assuming of course that 'fileUpload' is correctly specified AMD module.
EDIT: modified version of OP sample on Plunker:
https://plnkr.co/edit/QFckwndDicGpTfzhGwFC?p=preview
Note fileUpload.js module definition changed so that basic alert is displayed:
define([
"dijit/registry",
"dijit/Dialog",
"dijit/form/Button",
"dojo/domReady!"
], function (registry) {
return {
// Show the dialog
showDialog: function() {
//registry.byId("uploads").show();
alert("this is file upload mock");
}
}
});
as well as home.js hosting definition of whenClicked:
function whenClicked() {
require({
packages: [
{name: "fileUpload",
// location should point to fileUpload.js on your target server
location: "https://run.plnkr.co/uv2ILkhQpQC2wqRV",
main: "fileUpload"}
]},
['fileUpload'], function(fu) {
console.log("fileupload");
fu.showDialog();
});
}
Note that showing location of module is similar to what bRIMOs said in other answer. My approach configures however location only for this particular code wrapped by require; approach of bRIMOs is global.
Be aware however that Plunker is rebuilding location URL each time you reload editor :/ it effectively means tha you fix location prefix, run it fine, reload Plunker page, and it is broken again.
I think you missed to configure the path in dojo config in order to access the fileupload.js file by the AMD loader , In the dojoConfig doc there is many type of config ( basURl ,package , paths ...) , below you can see how to make configuration using packges , and dojo will load you files using require without any problem
So before loading your arcgis js api <script src="url_api_js"></script> be sur to do the folowing (configuring dojo with dojoConfig var)
<script type="text/javascript">
var dojoConfig = {
packages: [
{
name: "mypackage",
location: location.pathname.replace(/[^\/]+$/, '') +"/static"
}
]
};
<script>
<script src="url_api_js"></script>
and the inside your code use the name of package/name of file as below
require(['mypackage/fileUpload'], function(upload) {
upload.showDialog();
});
NB: the location could change , according to the server type , in this exemple the location is like : {location_name}/static/fileupload.js
hopefully this would help you .

Knockout Template is not working in IE 8 using RequireJS and Require Text

I have a problem rendering a tree object in JavaScript when using IE8. In order to write a hierarchy in an HTML page, I need to use a knockout template to recursively render the tree object.
I am using knockout components to render the final result using requirejs and require.text
The component is working fine in all browsers except for IE8. I know I should not be using IE8, but this is an intranet site for a company that cannot upgrade all browsers easily right now.
I simplified the code to only render a list of numbers in a plain list using the template.
Here is the ViewModel in test.vm.js:
define(['knockout'], function(ko) {
function TestViewModel() {
var self = this;
self.Title = "Example";
self.List = [
{ Name: "1" },
{ Name: "2" },
{ Name: "3" },
{ Name: "4" }
];
}
return TestViewModel;
});
Here is the View in test.view.js:
<script type="text/html" id="testTemplate">
<li data-bind="text: Name"></li>
</script>
<div>
<div data-bind="text: Title"></div>
<ul data-bind="template: {name: 'testTemplate', foreach: List}">
</ul>
</div>
And finally, the HTML page calling the component:
<html>
<head>
<meta charset="utf-8" />
<title>App</title>
<script src="Scripts/jquery-1.10.2.js"></script>
<script src="Scripts/require.js"></script>
</head>
<body>
<script type="text/javascript">
require.config({
baseUrl: "/",
paths: {
'knockout': 'Scripts/knockout-3.2.0',
'text': 'Scripts/require.text'
}
});
$(document).ready(function () {
require(['knockout'], function (ko) {
ko.applyBindings();
});
});
require(['knockout'], function (ko) {
ko.components.register('TestComponent', {
viewModel: { require: 'Scripts/test.vm' },
template: { require: 'text!Scripts/test.view.js' }
});
});
</script>
<!-- ko component: { name: "TestComponent" } -->
<!-- /ko -->
</body>
</html>
The main difference I can see browsing the DOM is that the template is rendered in other browsers completely
<script id="testTemplate" type="text/html">
<li data-bind="text: Name"></li>
</script>
while in IE8 is empty
<SCRIPT id=testTemplate type=text/html __ko__1453323521948="ko12"></SCRIPT>
If I put the template "testTemplate" outside the view file and directly in the HTML page, the component start to work. The component is not working just because the template is empty. Placing the "testTemplate" in the HTML is a partial solution but I need to find why it is not working when is placed inside the view file.
UPDATE:
I simplified the scenario. Apparently is some bug in the "template" parameter in component registration. If the template is enclosed in script tags, the content is ignored and not rendered in the page. If I decide to change it to use <template> tags, component tries to resolve data bindings inside the template and shows an error. Template tags are supposed to be ignored in bindings but the component does not ignore the tags. I tried with a temporal hack using the last tag I read is configured to be ignored by templates, by enclosing the template in textarea tags and placing display:none. Now the contents are rendered in all browsers but I don't like this solution.
I work for a company that still uses Windows XP so I'm limited to IE8 too.
We are successfully using knockout component templates with require.js and the text plugin.
Re. "If the template is enclosed in script tags, the content is ignored and not rendered in the page":
We have our templates inside HTML files, and use the !strip suffix to remove the wrapping HTML & body tags. This successfully loads the templates and also gives better editor support as the file extension is correct for editing markup.

KnockoutJS with Sammy.js SPA suggestion

I am building a SPA app with the following structure:
<body>
<!-- Main Container for our application -->
<div id="main">
</div>
<!-- End Main Container -->
<!-- Vendor Libraries -->
<script src="js/vendor/jquery/jquery-1.11.0.min.js"></script>
<script src="js/vendor/knockout/knockout-3.1.0.js"></script>
<script src="js/vendor/sammy/sammy-latest.min.js"></script>
<!-- Models -->
<script src="js/models/model1.js"></script>
<!-- ViewModels -->
<script src="js/viewmodels/viewModel1.js"></script>
<script src="js/viewmodels/viewModel2.js"></script>
<!-- App scripts -->
<script src="js/routes.js"></script>
<script src="js/app.js"></script>
</body>
The html file has a div which will hold the html for each respective page handled by Sammy.js wit the following code:
Sammy('#main', function() {
this.get('#/', function(context) {
context.$element().load('views/main1.html', function() {
ko.applyBindings(new ViewModel1(), $("#home")[0]);
});
});
this.get('#/text', function(context) {
context.$element().load('views/text.html', function() {
ko.applyBindings(new ViewModel2(), $("#home")[0]);
});
});
this.get('', function(context) {
this.redirect('#/');
});
}).run();
Each time I am loading the markup found in each html file and then apply my viewmodel.
My questions are:
Can you suggest any other possible way to load the markup apart from using jquery load().
Are my old bindings being disposed each time a new route is being called?
1: This question is very 'open'. There are tons of way to do this that aren't jquery.load. But the real question is: do you NEED another way? Do you need some form of control that $.load isn't giving you?
If you do, consider switching to jquery.get or jquery.ajax, and handle the request yourself. At the bottom of this post is an example.
2: No, because you keep applying the bindings to the same element. What you instead want to do is apply bindings to the first element WITHIN the container with id 'home'. Then, when you switch views, you want to do ko.removeNode on the view that you're removing. Below is a code example that illustrates how you can gain some more control over the process and clean up your bindings behind you.
function loadView(url, viewModel) {
$.get(url, function (response) {
var $container = $('#home'),
$view = $container.find('.view'),
$newView = $('<div>').addClass('view').html(response);
if ($view.length) {
ko.removeNode($view[0]); // Clean up previous view
}
$container.append($newView);
ko.applyBindings(viewModel, $newView[0]);
});
}
this.get('#/', function(context) {
loadView('views/main1.html', new ViewModel1());
});
this.get('#/', function(context) {
loadView('views/text.html', new ViewModel2());
});
What I did in this example is using jquery.get so we gain control over the whole process of loading and displaying the HTML. I then refactored part of the logic out into a separate function that is generic enough to use on every view you have.
When a view is retrieved I store it in an element with class 'view'. The bindings are applied to this element and it is stored in your container element. When switching views I clean up and remove the old view before the new view is added to the DOM.
This is just the beginning: In the loadView function, you can now try to call generic methods that you can implement on your viewmodels, such as 'activate' and 'deactivate'. You can also show a spinner instead of a view when a new view is being retrieved, etc.

Categories

Resources