Bootstrap collapse plugin and complexity in rendering pdf after expanding all collapsible elements - jquery-plugins

I have 3+ collapsed elements in my html. They have ids for e.g.collapse1, collapse2 and collapse3.
I have to generate a PDF with all the collapsed elements shown (expanded).
Some of the elements might be expanded by user. So if an element is not expanded
I need to use javascript to expand and show the item and finally render the PDF.
There are few problems I faced.
If I use javascript collapse plugin, and show collapsed item, its asynchronous.
I have to subscribe to "shown.bs.collapse" event and inside the event handler I have to render PDF.
"shown.bs.collapse" event is not fired when an element is already opened. Because of this
I need to create flags to determine whether element is already shown and need to write if / else
logic to resolve this.
Below is the complex logic I wrote (its only pseudo code). Can this be simplified?
downloadPDF() {
flag1 = collapse1.hasClass('show')
flag2 = collapse2.hasClass('show')
flag3 = collapse3.hasClass('show')
$("#collapse1").on('shown.bs.collapse', function() {
if (!flag2)
collapse2.show()
else
if (!flag3) {
collapse3.show();
} else {
generatePDF()
}
}
$("#collapse2").on('shown.bs.collapse', function(){
if (!flag3)
collapse3.show()
else
generatePDF()
}
$("#collapse3").on('shown.bs.collapse', function(){
generatePDF()
}
if (!flag1) {
collapse1.show()
}
if (flag1 && !flag2) {
collapse2.show();
}
if (flag1 && flag2 && !flag3) {
collapse3.show();
}
if (flag1 && flag2 && flag3) {
generatePDF();
}
}

Related

How do I update model in Master detail razor page with AJAX results

I have a parent table with rows.
When they select a row, an AJAX call fires that returns the child details.
I have multiple text boxes showing child properties
<div class="row">
#Html.CheckBoxFor(m => m.Child.Property)
#Html.LabelFor(m => m.Child.Property)
</div>
but I can't see how to update the text boxes with the child I get back in AJAX results.
The best I've been able to do is manually updating each field from the 'complete' method. But I've got about 30 more fields to add and it feels like the wrong approach.
How do I bind the edit boxes to the returned model without using partials and without refreshing the entire page?
I added Child as a property in the #model, and the TextFor appears to bind properly. But of course
#Model.Child = child
does not. So they never show any data.
This question was based on a misunderstanding on my part. At first I deleted the question when I realized my mistake. I'm reinstating it because I think it is an easy mistake for a noobie to fall into and once you do, the answer is hard to sort out.
The problem is that #model no longer exists once the page is rendered. There is no data binding going on behind the scenes as I thought there was.
Your options are
populating the elements manually. (this will need editing to fit your particular elements)
function DisplayMergeSection(data) {
for (var key of Object.keys(data)) {
DisplayElement(data, key, "#Clients_");
}
}
function DisplayElement(data, key, prefix) {
return;
var val = data[key];
var valString = data[key + "String"];
var element = $(prefix + key)[0];
if (element && element.type === 'text') {
if ((val || '').toString().indexOf("Date(") > -1) {
var dtStart = new Date(parseInt(val.substr(6)));
element.value = moment(dtStart).format('MM/DD/YYYY');
} else {
element.value = val;
}
} else if (element && element.type === 'checkbox') {
element.checked = val;
} else if (element && element.type === 'select-one') {
element.value = valString;
} else if (element && element.nodeName === 'DIV') {
if ((val || '').toString().indexOf("Date(") > -1) {
var dtStart = new Date(parseInt(val.substr(6)));
element.innerText = moment(dtStart).format('MM/DD/YYYY');
} else {
element.innerText = val;
}
}
}
create a bunch of observables with knockout and then set up data binding. This is a lot cleaner.
https://knockoutjs.com/documentation/json-data.html
set up a mapping with the knockout plugin.
https://knockoutjs.com/documentation/plugins-mapping.html

Trouble w/ Meteor Sorting

I'm trying to add a simple drop down control above a list such that I can sort it by "created" or "title".
The list template is called posts_list.html. In it's helper .js file I have:
posts: function () {
var sortCriteria = Session.get("sortCriteria") || {};
return Posts.find({},{sort: {sortCriteria: 1}});
}
Then, I have abstracted the list into another template. From here I have the following click event tracker in the helper.js
"click": function () {
// console.log(document.activeElement.id);
Session.set("sortCriteria", document.activeElement.id);
// Router.go('history');
Router.render('profile');
}
Here I can confirm that the right Sort criteria is written to the session. However, I can't make the page refresh. The collection on the visible page never re-sorts.
Frustrating. Any thoughts?
Thanks!
You can't use variables as keys in an object literal. Give this a try:
posts: function() {
var sortCriteria = Session.get('sortCriteria');
var options = {};
if (sortCriteria) {
options.sort = {};
options.sort[sortCriteria] = 1;
}
return Posts.find({}, options);
}
Also see the "Variables as keys" section of common mistakes.
thanks so much for that. Note I've left commented out code below to show what I pulled out. If I required a truly dynamic option, versus the simply binary below, I would have stuck w/ the "var options" approach. What I ended up going with was:
Template.postList.helpers({
posts: function () {
//var options = {};
if (Session.get("post-list-sort")) {
/*options.sort = {};
if (Session.get("post-list-sort") == "Asc") {
options.sort['created'] = 1;
} else {
options.sort['created'] = -1;
}*/
//return hunts.find({}, options);}
console.log(Session.get("hunt-list-sort"));
if (Session.get("hunt-list-sort") == "Asc") {
return Hunts.find({}, {sort: {title: 1}});
}
else {
return Hunts.find({}, {sort: {title: -1}});
};
}
}
});

Creating a new kendo binding for "associative arrays"

I'll start off by stating that I don't know if this is possible at all, but I'm reading over the Kendo UI documentation and trying to figure out how to at least try it, but I'm running into a lot of difficulties with making a custom binding. This is a followup to another question I am still working on, which is posted here. If this is not an appropriate question, please kindly let me know, and I will close it or rephrase it. I'm just really lost and confused at this point.
As I understand it, based on what I've been told and tried, Kendo cannot bind to an Associative Array not because the data isn't good, but because it is an array of objects, each as a separate individual entity - under normal circumstances, an array would be a bit different and contain a length property, as well as some other functions in the array prototype that make iteration through it possible.
So I was trying to conjecture how to get around this. I succeeded in getting what I think was a workaround to function. I preface that with "think" because I'm still too inexperienced with Javascript to truly know the ramifications of doing it this way (performance, stability, etc)
Here is what I did;
kendo template
<script type="text/x-kendo-template" id="display-items-many">
# for(var key in data) { #
# if (data.hasOwnProperty(key) && data[key].hasOwnProperty("Id")) { #
<tr>
<td>
<strong>#= data[key].Id #</strong>
</td>
<td class="text-right">
<code>#= data[key].Total #</code>
</td>
</tr>
# } #
# } #
</script>
html
<table class="table borderless table-hover table-condensed" data-bind="source: Associative data-template="display-items-many">
</table>
Now to me, immediately off hand, this gave me the illusion of functioning. So I got to thinking a bit more on how to fix this ...
I want to create a new binding called repeat. The goal of this binding is as follows;
repeat the template for each instance of an object within the given root object that meets a given criteria
In my head, this would function like this;
<div data-template="repeater-sample" data-bind="repeat: Associative"></div>
<script type="text/x-kendo-template" id="repeater-sample">
<div> ${ data.Id }</div>
</script>
And the criteria would be a property simply called _associationKey. So the following would, in theory, work.
$.ajax({
// get data from server and such.
}).done(function(results){
// simple reference to the 'associative array' for easier to read code
var associative = results.AssociativeArray;
// this is a trait that everything in the 'associative array' should have to match
// this is purely, purely an example. Obviously you would use a more robust property
var match = "Id";
// go through the results and wire up the associative array objects
for(var key in associative ) {
if(associative.hasOwnProperty(key) && associative[key].hasOwnProperty(match)) {
associative[key]._associationKey = 10; // obviously an example value
}
}
// a watered down example implementation, obviously a real use would be more verbose
viewModel = kendo.observable({
// property = results.property
// property = results.property
associativeArray = associative
});
kendo.bind('body', viewModel);
});
So far this actually seems to work pretty well, but I have to hard code the logic in the template using inline scripting. That's kind of what I want to avoid.
Problem
The big issue is that I'm vastly confused on telerik's documentation for custom bindings (available here). I do have their examples to draw from, yes - but it's a bit confusing to me how it interacts with the object. I'll try to explain, but I'm so lost that it may be difficult.
This is what telerik gives for an example custom binding, and I've pruned it a bit for space concerns;
<script>
kendo.data.binders.repeater = kendo.data.Binder.extend({
init: function(element, bindings, options) {
//call the base constructor
kendo.data.Binder.fn.init.call(this, element, bindings, options);
var that = this;
// how do we interact with the data that was bound?
}
});
</script>
So essentially that's where I am lost. I'm having a big disconnect figuring out how to interact with the actual "associative array" that is bound using data-bind="repeat: associativeArray"
So ..
I need to interact with the bound data (the entire 'associative array')
I need to be able to tell it to render the target template for each instance that matches
Further Updates
I have been digging through the kendo source code, and this is what I have so far - by taking the source binding as an example... but I'm still not getting the right results. Unfortunately this poses a few problems;
some of the functions are internal to kendo, I'm not sure how to get access to them without re-writing them. While I have the source and can do that, I'd prefer to make version agnostic code so that it can "plug in" to newer releases
I'm totally lost about what a lot of this does. I basically made a copy of the source binding and replaced it with my own syntax where possible, since the concept is fundamentally the same. I cannot figure out where to do the test for qualification to be rendered, if that makes sense.
I'm having a big logic disconnect here - there should ideally be some place where I can basically say ... If the current item that kendo is attempting to render in a template matches a criteria, render it. If not, pass it over and then another place where I tell it to iterate over every object in the 'associative array' so as to get to the point where I test it.
I feel just forcing a for loop in here will actually make this fire too many times, and I am getting pretty lost. Any help is greatly appreciated.
kendo.data.binders.repeat = kendo.data.Binder.extend({
init: function(element, bindings, options) {
kendo.data.Binder.fn.init.call(this, element, bindings, options);
var source = this.bindings.repeat.get();
if (source instanceof kendo.data.DataSource && options.autoBind !== false) {
source.fetch();
}
},
refresh: function(e) {
var that = this,
source = that.bindings.repeat.get();
if (source instanceof kendo.data.ObservableArray|| source instanceof kendo.data.DataSource) {
e = e || {};
if (e.action == "add") {
that.add(e.index, e.items);
} else if (e.action == "remove") {
that.remove(e.index, e.items);
} else if (e.action != "itemchange") {
that.render();
}
} else {
that.render();
}
},
container: function() {
var element = this.element;
if (element.nodeName.toLowerCase() == "table") {
if (!element.tBodies[0]) {
element.appendChild(document.createElement("tbody"));
}
element = element.tBodies[0];
}
return element;
},
template: function() {
var options = this.options,
template = options.template,
nodeName = this.container().nodeName.toLowerCase();
if (!template) {
if (nodeName == "select") {
if (options.valueField || options.textField) {
template = kendo.format('<option value="#:{0}#">#:{1}#</option>',
options.valueField || options.textField, options.textField || options.valueField);
} else {
template = "<option>#:data#</option>";
}
} else if (nodeName == "tbody") {
template = "<tr><td>#:data#</td></tr>";
} else if (nodeName == "ul" || nodeName == "ol") {
template = "<li>#:data#</li>";
} else {
template = "#:data#";
}
template = kendo.template(template);
}
return template;
},
add: function(index, items) {
var element = this.container(),
parents,
idx,
length,
child,
clone = element.cloneNode(false),
reference = element.children[index];
$(clone).html(kendo.render(this.template(), items));
if (clone.children.length) {
parents = this.bindings.repeat._parents();
for (idx = 0, length = items.length; idx < length; idx++) {
child = clone.children[0];
element.insertBefore(child, reference || null);
bindElement(child, items[idx], this.options.roles, [items[idx]].concat(parents));
}
}
},
remove: function(index, items) {
var idx, element = this.container();
for (idx = 0; idx < items.length; idx++) {
var child = element.children[index];
unbindElementTree(child);
element.removeChild(child);
}
},
render: function() {
var source = this.bindings.repeat.get(),
parents,
idx,
length,
element = this.container(),
template = this.template();
if (source instanceof kendo.data.DataSource) {
source = source.view();
}
if (!(source instanceof kendo.data.ObservableArray) && toString.call(source) !== "[object Array]") {
source = [source];
}
if (this.bindings.template) {
unbindElementChildren(element);
$(element).html(this.bindings.template.render(source));
if (element.children.length) {
parents = this.bindings.repeat._parents();
for (idx = 0, length = source.length; idx < length; idx++) {
bindElement(element.children[idx], source[idx], this.options.roles, [source[idx]].concat(parents));
}
}
}
else {
$(element).html(kendo.render(template, source));
}
}
});
I would propose as a simpler solution transform transmitted associative array in an array. This is pretty simple and (for most cases) can solve your problem.
Lets say that you get the following associative array received from the server:
{
"One" : { Name: "One", Id: "id/one" },
"Two" : { Name: "Two", Id: "id/two" },
"Three" : { Name: "Three", Id: "id/three" }
}
That is store in a variable called input. Transform it from associative to no associative is as easy as:
var output = [];
$.each(input, function(idx, elem) {
elem.index = idx;
output.push(elem);
});
Now, you have in output an equivalent array where I saved the index field into a field called index for each element of the associative array.
Now you can use out-of-the-box code for displaying the data received from the server.
See it in action here : http://jsfiddle.net/OnaBai/AGfWc/
You can even use KendoUI DataSource for retrieving and transforming the data by using DataSource.schema.parse method as:
var dataSource = new kendo.data.DataSource({
transport: {
read: ...
},
schema : {
parse: function (response) {
var output = [];
$.each(response, function(idx, elem) {
elem.index = idx;
output.push(elem);
});
return output;
}
}
});
and your model would be:
var viewModel = new kendo.data.ObservableObject({
Id: "test/id",
Associative: dataSource
});
You can see it in action here: http://jsfiddle.net/OnaBai/AGfWc/1/

Loop through all buttons on screen for a given tag, WP7, using C#

I'm looking for a simple way to loop through all buttons onscreen for a given tag. Eg "foo". I'm using WP7, using C#. I'm very new to the platform so go easy on me :P
Googling this sort of thing isn't really working out for me either - I think I have my terminology wrong, so any tips on that too would be appreciated.
You should probably loop through all the controls on your page, check whether each one is a button, and if so check its Tag property.
Something like this...
foreach (UIElement ctrl in ContentPanel.Children)
{
if (ctrl.GetType() == typeof(Button))
{
Button potentialButton = ((Button)ctrl);
if (potentialButton.Tag = Tag)
return (Button)ctrl;
}
}
Bear in mind, though, that if you have nested controls on the page, you will need to think about recursing into any item with children to make sure you catch all the controls.
First, create a method to enumerate recursively the controls in your page:
public static IEnumerable<FrameworkElement> FindVisualChildren(FrameworkElement control)
{
if (control == null)
{
yield break;
}
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(control); i++)
{
var child = System.Windows.Media.VisualTreeHelper.GetChild(control, i) as FrameworkElement;
if (child != null)
{
yield return child;
foreach (var grandChild in FindVisualChildren(child))
{
yield return grandChild;
}
}
}
}
Then call it and keep only the controls you want:
var buttons = FindVisualChildren(this.ContentPanel)
.OfType<Button>()
.Where(b => b.Tag is string && (string)b.Tag == "foo");
(where ContentPanel is the root element of your page)

Waypoint unrecognized on Ajax-loaded content

I'm loading a page into a div. I'm also attempting to establish a waypoint, so that when the user scrolls down the page, the menu will change colors.
The problem I am having is the new height of the div is not recognized by the browser once the ajax content is loaded.
Here's what I have:
$(".cta").live('click', function () {
$('#faq').load('about-us/faqs/index.html'),
function () {
$("#faq").waypoint(function (event, direction) {
if (direction === 'up') {
$("#siteNav li a").removeClass("siteNavSelected");
$("#siteNav li.nav3 a").addClass("siteNavSelected");
}
}, {
offset: function () {
return $.waypoints('viewportHeight') - $("#faq").outerHeight();
}
});
}
return false;
});
Any ideas? Thanks.
Use $.waypoints('refresh');, from the documentation:
This will force a recalculation of each waypoint’s trigger point based on its offset option. This is called automatically whenever the window is resized or new waypoints are added. If your project is changing the DOM or page layout without doing one of these things, you may want to manually call it.
I'm not familiar with the intrinsics of the waypoint plugin, but you could also bind a scroll event and then capture the .scrollTop() value. Would look something like this:
$(document).bind('scroll', function(event) {
var scrollTop = $(window).scrollTop();
if (scrollTop < 1000 && $('siteNav li').hasClass('styleA')) { return; }
else {
$('siteNav li').removeClass('styleB');
$('siteNav li').addClass('styleA');
}
if (scrollTop > 1000 && $('siteNav li').hasClass('styleB')) { return; }
else {
$('siteNav li').removeClass('styleA');
$('siteNav li').addClass('styleB');
}
});
You have to play with the values a little to get it acting at the right spot. Also you have to use a greater or less than value in the test as if a user is at the top of the page and uses the scroll-wheel on his mouse to fly down the page, you don't get every value in between.

Resources