I would like to have a nice interactive grid view in an HTML page. I am using nodejs, express, twitterbootstrap, knockoutjs, for my technology stack. I am trying to use KOGrid to display various data points with some nice built in column sorting and other grid functionality.
My issue is trying to fire an event when a button is clicked in a row. And pass to that event, some of the various data fields from that specific row. So in KOGrid specifics, I am using cellTemplates and I need to call some function in the onclick event, but pass that function some KOGRID data bounded values. So, in my input element I would have
data-bind="onclick: [Name of my function]( [name of some data bounded variable], [name of some other data bounded variable])
Can someone show me how to do this?
Here is a sample of my code...the input/onclick in the CBTemplate is where I am having issues.
CBTEMPLATE:
<script type="text/html" id="actionTemplate">
<div data-bind="kgCell: $cell">
<input type="checkbox" value="1" class="checkbox" checked="checked" data-bind="onclick: 'MyOnClickFunction( siteId(), status() )'"/>
</div>
</script>
DIV TAG:
<div data-bind="koGrid: { data: offer.siteCounts,
columnDefs: [ { field: 'templateField0', displayName: 'Site', cellTemplate: 'siteTemplate', width: 150},
{ field: 'status', displayName: 'Current Status', cellClass: 'site', cellTemplate: 'statusTemplate', width: 115},
{ field: 'details', displayName: 'Details', width: 175},
{ field: 'actionField0', displayName: 'Action', cellTemplate: 'cbTemplate', width: 200}],
autogenerateColumns: false,
displaySelectionCheckbox: false,
isMultiSelect: false }">
</div>
The click event binding should look like this with KO:
data-bind="click: function(data,event) { MyOnClickFunction(siteId(), status()) }"
Here is a working JSFiddle where you can play with it.
Some sidenotes:
In my sample I have used the $root binding context property to access the sample function: $root.offer.MyOnClickFunction. You will need to adjust this depending on which level you have defined the MyOnClickFunction on your viewmodels.
You need to return true from the click binding handler if you do want to let the default click action proceed (e.g checking the checkbox etc.)
Related
I have a webgrid with a hyperlink column and upon clicking that link it should open a modal popup I have a modal named #examplemodal in a partial view named"GetDetails". Below I try to open the modal from a controller action method that returns partial view.
#Html.ActionLink("OrderNumber","GetDetails","Home",
new{id = item.ID}, new{data_target="#exampleModal", data_toggle="modal", #class="modal-backdrop"});
When I click on the link with Ordernumber screen blacks out and I dont see the grid at all. Any pointers on where I am doing a mistake. I am using asp.Net mvc5 and bootstrap v4.3.1
I think your concept is totally wrong. I assume you want to display the order details in a modal? And since you have a method to return a partial view for that already, you want to load that order details content into modal whenever the user clicks the hyperlink column?
If that's the case, bootstrap modal is not the right tool for you. It's designed to load static content. If you want to load dynamic content, i.e., order details for different order numbers, you should look into a concept called iframe, and libraries like Fancybox, etc.
Here's what I would do:
1.Define a modal layout
Because you want to display the partial view on a modal, you generally don't want to have things like sidebar, top navigation, etc, from your site layout. Hence I will define a layout for modals.
<!-- _PopupLayout.cshtml -->
<!DOCTYPE html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" />
<!-- All your necessary styles, meta data, etc -->
<title>...</title>
#RenderSection("css", required: false)
</head>
<body>
<main class="container-fluid">
#RenderBody()
</main>
<!-- All your necessary javascripts -->
#RenderSection("scripts", required: false)
</body>
</html>
2.Return views that use _PopupLayout
I know you've created partial views. But regular view is fine. In fact, it's better because you can setup the layout the regular view uses, as well as the view models for that.
Because you want this view to look like a bootstrap modal, you should construct your view using bootstrap modal structure.
#model ...
#{
ViewData["Title"] = "Order Details";
Layout = "~/Views/Shared/_PopupLayout.cshtml";
}
<div class="modal-header">
<h5 class="modal-title">Order Details</h5>
</div>
<div class="modal-body">
...
</div>
3.Write JavaScript to trigger FancyBox on link clicking
You can use a custom css class for the selector for all links you want to load the iframe from. In my case I call it .popup-fancy. You can also define multiple classes for popping up different sizes of modals/fancybox modals.
$(function() {
$().fancybox({
selector: 'a.popup-fancy',
defaultType: 'iframe',
baseClass: 'fancybox-md',
iframe: {
preload: false
},
arrows: false,
infobar: false,
smallBtn: true
});
$().fancybox({
selector: 'a.popup-fancy-lg',
defaultType: 'iframe',
baseClass: 'fancybox-lg',
iframe: {
preload: false
},
arrows: false,
infobar: false,
smallBtn: true
});
$().fancybox({
selector: 'a.popup-fancy-xl',
defaultType: 'iframe',
baseClass: 'fancybox-xl',
iframe: {
preload: false
},
arrows: false,
infobar: false,
smallBtn: true
});
});
See how it sets the default type to iframe? You can find those configuration options from Fancybox documentation. Not to forgot those 3 base classes styles (I'm using Sass):
.fancybox-md {
.fancybox-content {
max-width: 36.75rem;
}
}
.fancybox-lg {
.fancybox-content {
max-width: 65.625rem;
}
}
.fancybox-xl {
.fancybox-content {
max-width: 78.75rem;
}
}
4.Create links to open modal
Now you can create links with any of those fancybox trigger classes:
<a href="#Url.Action("details", "order", new { area = "", id = item.Id })"
class="popup-fancy">
See Order Details
</a>
I assume you have the order controller and details action method all setup to return a view that uses the _PopupLayout, then when the user clicks on the link, instead of the regular redirect to the page using standard layout, the page content should be loaded into the fancybox modal.
For example:
If you can only use bootstrap modal??
In that case, you will have to create a modal template (probably in the layout so that it can be called anywhere) with an iframe inside. And then on link clicked, you use javascript to set the source of the iframe and manually popup the modal.
Sample of modal template
<div id="fancy-modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<iframe src="" frameborder="0"></iframe>
</div>
</div>
</div>
Then on the page where you generate links, instead to generate actual links, you will have to generate the modal triggers:
<a href="#" class="fancy-modal-trigger"
data-iframe-src="#Url.Action("details", "order", new { area = "", id = item.Id })">
See Order Details
</a>
See here you put the actual link to your view on a data-attribute instead of href, because you don't want the link to actually navigate to the destination.
$(function() {
$('a.fancy-modal-trigger').click(function() {
let iframeSrc = $(this).data('iframe-src'),
$fancyModal = $('#fancy-modal');
$fancyModal.find('iframe').prop('src', iframeSrc);
$fancyModal.modal('show');
return false;
});
});
DISCLAIM: this is not yet tested.
I am trying to created a nested repeater or a nested list view using WinJS 4.0, but I am unable to figure out how to bind the data source of the inner listview/repeater.
Here is a sample of what I am trying to do (note that the control could be Repeater, which I would prefer):
HTML:
<div id="myList" data-win-control="WinJS.UI.ListView">
<span data-win-bind="innerText: title"></span>
<div data-win-control="WinJS.UI.ListView">
<span data-win-bind="innerText: name"></span>
</div>
</div>
JS:
var myList = element.querySelector('#myList).winControl;
var myData = [
{
title: "line 1",
items: [
{name: "item 1.1"},
{name: "item 1.2"}
]
},
{
title: "line 2",
items: [
{name: "item 2.1"},
{name: "item 2.2"}
]
}
];
myList.data = new WinJS.Binding.List(myData);
When I try this, nothing renders for the inner list. I have attempted trying to use this answer Nested Repeaters Using Table Tags and this one WinJS: Nested ListViews but I still seem to have the same problem and was hoping it was a little less complicated (like KnockOut).
I know it is mentioned that WinJS doesn't support nested ListViews, but that seems to be a few years ago and I am hoping that is still not the issue.
Update
I was able to get the nested repeater to work correctly, thanks to Kraig's answer. Here is what my code looks like:
HTML:
<div id="myTemplate" data-win-control="WinJS.Binding.Template">
<div
<span>Bucket:</span><span data-win-bind="innerText: name"></span>
<span>Amount:</span><input type="text" data-win-bind="value: amount" />
<button class="removeBucket">X</button>
<div id="bucketItems" data-win-control="WinJS.UI.Repeater"
data-win-options="{template: select('#myTemplate')}"
data-win-bind="winControl.data: lineItems">
</div>
</div>
</div>
<div id="budgetBuckets" data-win-control="WinJS.UI.Repeater"
data-win-options="{data: Data.buckets,template: select('#myTemplate')}">
</div>
JS: (after the "use strict" statement)
WinJS.Namespace.define("Data", {
buckets: new WinJS.Binding.List([
{
name: "A",
amount: 5,
lineItems: new WinJS.Binding.List( [
{ name: 'test item1', amount: 50 },
{ name: 'test item2', amount: 25 }
]
)
}
])
})
*Note that this answers part of my question, however, I would really like to do this all after a repo call and set the repeater data source programmatically. I am going to keep working towards that and if I get it I will post that as the accepted answer.
The HTML Repeater control sample for Windows 8.1 has an example in scenario 6 with a nested Repeater, and in this case the Repeater is created through a Template control. That's a good place to start. (I discuss this sample in Chapter 7 of Programming Windows Store Apps with HTML, CSS, and JavaScript, 2nd Edition, starting on page 372, or 374 for the nested part.)
Should still work with WinJS 4, though I haven't tried it.
Ok, so I have to give much credit to Kraig because he got me on the correct path to getting this worked out and the referenced book Programming Windows Store Apps with HTML, CSS, and JavaScript, 2nd Edition is amazing.
The original issue was a combination of not using templates correctly (using curly braces in the data-win-bind attribute), not structuring my HTML correctly and not setting the child lists as WinJS.Binding.List data source. Below is the final working code structure to created a nested repeater when binding the data from code only:
HTML:
This is the template for the child lists. It looks similar, but I plan on add more things so I wanted it separate instead of recursive as referenced in the book. Note that the inner div after the template control declaration was important for me.
<div id="bucketItemTemplate" data-win-control="WinJS.Binding.Template">
<div>
<span>Description:</span>
<span data-win-bind="innerText: description"></span>
<span>Amount:</span>
<input type="text" data-win-bind="value: amount" />
<button class="removeBucketItem">X</button>
</div>
</div>
This is the main repeater template for the lists. Note that the inner div after the template control declaration was important for me. Another key point was using the "winControl.data" property against the property name of the child lists.
<div id="bucketTemplate" data-win-control="WinJS.Binding.Template">
<div>
<span>Bucket:</span>
<span data-win-bind="innerText: bucket"></span>
<span>Amount:</span>
<input type="text" data-win-bind="value: amount" />
<button class="removeBucket">X</button>
<div id="bucketItems" data-win-control="WinJS.UI.Repeater"
data-win-options="{template: select('#bucketItemTemplate')}"
data-win-bind="winControl.data: lineItems">
</div>
</div>
</div>
This is the main control element for the nested repeater and it is pretty basic.
<div id="budgetBuckets" data-win-control="WinJS.UI.Repeater"
data-win-options="{template: select('#bucketTemplate')}">
</div>
JavaScript:
The JavaScript came down to a few simple steps:
Getting the winControl
var bucketsControl = element.querySelector('#budgetBuckets').winControl;
Looping through the elements and making the child lists into Binding Lists - the data here is made up but could have easily came from the repo:
var bucketsData = selectedBudget.buckets;
for (var i = 0; i < bucketsData.length; i++) {
bucketsData[i].lineItems =
new WinJS.Binding.List([{ description: i, amount: i * 10 }]);
}
Then finally converting the entire data into a Binding list and setting it to the "data" property of the winControl.
bucketsControl.data = new WinJS.Binding.List(bucketsData);
*Note that this is the entire JavaScript file, for clarity.
(function () {
"use strict";
var nav = WinJS.Navigation;
WinJS.UI.Pages.define("/pages/budget/budget.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
// TODO: Initialize the page here.
var bindableBuckets;
require(['repository'], function (repo) {
//we can setup our save button here
var appBar = document.getElementById('appBarBudget').winControl;
appBar.getCommandById('cmdSave').addEventListener('click', function () {
//do save work
}, false);
repo.getBudgets(nav.state.budgetSelectedIndex).done(function (selectedBudget) {
var budgetContainer = element.querySelector('#budgetContainer');
WinJS.Binding.processAll(budgetContainer, selectedBudget);
var bucketsControl = element.querySelector('#budgetBuckets').winControl;
var bucketsData = selectedBudget.buckets;
for (var i = 0; i < bucketsData.length; i++)
{
bucketsData[i].lineItems = new WinJS.Binding.List([{ description: i, amount: i * 10 }]);
}
bucketsControl.data = new WinJS.Binding.List(bucketsData);
});
});
WinJS.UI.processAll();
}
});
})();
I have problems with grouped ListView or short group. i get data from webservice already but i don't know how to binding data to html template user behind code.
HTML:
<div class="listLayoutTopHeaderTemplate" data-win-control="WinJS.Binding.Template">
<div class="listLayoutTopHeaderTemplateRoot">
<div data-win-bind="innerHTML: title"></div>
</div>
</div>
<div id="listView"
class="win-selectionstylefilled"
data-win-control="WinJS.UI.ListView"
data-win-options="{
itemTemplate: select('.smallListIconTextTemplate'),
groupHeaderTemplate: select('.listLayoutTopHeaderTemplate'),
layout: { type: WinJS.UI.ListLayout, groupHeaderPosition: 'top'}}">
</div>
Winjs:
var myData = new WinJS.Binding.List([
{ title: "Banana Blast", text: "Low-fat frozen yogurt", picture: "/images/fruits/60Banana.png" },
{ title: "Lavish Lemon Ice", text: "Sorbet", picture: "/images/fruits/60Lemon.png" },
]);
var grouped = myData.createGrouped(function (item) {
return item.title.toUpperCase().charAt(0);
}, function (item) {
return {
title: item.title.toUpperCase().charAt(0)
};
}, function (left, right) {
return left.charCodeAt(0) - right.charCodeAt(0);
});
listView.winControl.groupDataSource = grouped.groupDataSource;
You need to take whatever data you get from the web service, which is presumably JSON, and transfer that data into a WinJS.Binding.List that you can use as the ListView's data source. In the code you show, this is the myData variable, which you can initialize as empty (instead of using the shown data that's just from a sample). Iterating over your web service data, call the List's add method for each item. If the JSON you want to render already has an array, then parse that out and pass the array to the WinJS.Binding.List constructor.
Either way, you end up with a WinJS.Binding.List populated with the data from the service, which you can then give to the ListView for rendering.
There are fancier ways of working with data sources if you want to avoid obtaining the web service data and copying it all into an in-memory WinJS.Binding.List. For this I'll refer you to Chapter 7 of my free ebook, Programming Windows Store Apps with HTML, CSS, and JavaScript, 2nd Edition, specifically the section "Collection Control Data Sources" on page 380.
Following the documentation of the YUI DataTable control i've inferred the following code:
<!DOCTYPE HTML>
<HTML>
<HEAD>
<SCRIPT type="text/javascript" src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></SCRIPT>
<SCRIPT type="text/javascript">
// Create a new YUI instance and populate it with the required modules.
YUI().use('datatable', function (Y) {
// Columns must match data object property names
var data = [
{ id: "ga-3475", name: "gadget", price: "$6.99", cost: "$5.99" },
{ id: "sp-9980", name: "sprocket", price: "$3.75", cost: "$3.25" },
{ id: "wi-0650", name: "widget", price: "$4.25", cost: "$3.75" }
];
var table = new Y.DataTable({
columns: ["id", "name", "price"],
data: data,
// Optionally configure your table with a caption
caption: "My first DataTable!",
// and/or a summary (table attribute)
summary: "Example DataTable showing basic instantiation configuration"
});
table.render("#example");
});
</SCRIPT>
</HEAD>
<BODY>
</BODY>
</HTML>
The insulting thing is that the documentation says:
This code produces this table:
except that this code produces this table:
So obviously i'm missing something pretty fundamental about how to render a YUI data table. What is the correct way to render a YUI data table?
Q. How to render a YUI datatable?
Another page mentions including a <div>, changing my <BODY> from empty to:
<BODY>
<div class="example yui3-skin-sam">
<div id="simple"></div>
<div id="labels"></div>
</div>
</BODY>
but does not change the look of the control.
Add class="yui3-skin-sam" in body tag, table css is written corresponding to this class.
Move the <script>s to the bottom of the <body>, or at least after the <div> that will contain the DataTable. That will avoid a race condition where the scripts may be loaded before the DOM is set up.
render('#example') is telling the DataTable to render into an element with an id of 'example' The markup sample you included has a div with a class of 'example', then two divs with ids 'simple' and 'labels'. You need to make sure you're rendering inside a parent element with class yui3-skin-sam. If you tell a YUI widget to render into an element it can't find, it falls back to rendering it inside the <body>. You can fix this in a few ways:
add the class to the <body> tag instead of a <div> (not a bad idea, but you should still fix the render target selector)
use a render(?) target selector that matches an element on the page, such as render('.example'), render('#simple'), or render('#labels').
In any case, make sure your render target is inside an element with class="yui3-skin-sam"
I have a menu-button that I want to remove the drop-shadow from. I would like to not have to instantiate a menu object first (since it requires me to specify a div in the DOM to attach it to). I am able to instantiate a menu in the JS, but when I try to remove the drop shadow via: this.menuButton.getMenu().cfg.setProperty('shadow', false); the drop shadow still appears. I checked the cfg object in the JS debugger and shadow is set to false, but the shadow still appears.
Another option would be to remove the shadow div from the dom, but that doesn't seem like the right thing to do.
I did eventually create a div in my DOM to hold my menu. I was wary at first because my layout is not very robust (we outsourced to a graphic designer and have been trying to integrate his work with our code); this was why I didn't want to introduce a div into the DOM.
The key for me was to include the div in a place where it wouldn't affect the layout. Originally I had something that looked like this:
<input> type="button" id="srchType" />
<input type="text" id="first-name" value="First Name" />
<input type="submit" id="profiles-search-submit" value="Search" />
I inserted the div between two of the inputs; this screwed up my layout. The key was to insert the div before or after; this wouldn't screw up my layout. YMMV, depending on how sensitive your DOM is to changes; I hope this helps someone out if they're wondering what they should do.
After you do this (create your div) you can create a YUI Menu object like so:
var searchMenuItems =
[{ text: "First Name", value: 'firstName', onclick: {fn: onMenuItemClick}},
{ text: "Last Name", value: 'lastName', onclick: {fn: onMenuItemClick}}];
var srchTypeMenuConfig = { shadow: false,
effect: {
effect: YAHOO.widget.ContainerEffect.FADE,
duration: .25
}
};
this.srchTypeMenu = new YAHOO.widget.Menu(this.searchMenuEl,
srchTypeMenuConfig);