views.list = Ractive.extend({/*...*/});
var Content = Ractive.extend({
template: '#template-content',
components: {
//view: views['list']
},
onrender: function() {
/* call this.request */
},
request: function(route) {
var self = this;
$.ajax({
url: route,
type: 'GET',
data: {},
dataType: 'json',
success: function(data){
self.components.view = views[data.view];
}
});
}
});
Ajax request returns {view:'list'}. I write in the console: "Content.components.view" and see:
function ( options ) {
initialise( this, options );
}
But "Content.findComponent('view')" returns "null".
It will work if I uncomment the line "//view: views['list']" in the components. How to dynamically change the component?
Update 1:
I thought I can do this:
var Content = Ractive.extend({
components: {
// Aaaand add listener for data.view?
view: function(data) {
return views[data.view];
// But no, it is not updated when changes data.view ...
}
},
data: {},
oninit: function() {
var self = this;
self.set('view', 'list');
},
});
Not work.. Why then need "data" argument?
I won :)
// Create component globally
Ractive.components.ListView = Ractive.extend({});
// Create ractive instance
var Content = Ractive.extend({
template: '#template-content',
partials: {
view: 'Loading...' // So... default
},
oninit: function() {
var self = this;
$.ajax({
...
success: function(data){
// Hardcore inject component
self.partials.view = '<'+data.view+'/>';
// Little hack for update partial in template
self.set('toggle', true);
self.set('toggle', false);
}
});
},
});
In Content template:
{{^toggle}}{{>view}}{{/}}
It's works-fireworks!
Related
I am trying to populate form items based on a dropdown selection without refreshing the entire page or loading a new view. This was fairly simple in web forms using an UpdatePanel, for instance. However, I can't get this to work in MVC.
I've tried many of the solutions I could find. I don't know if this is a DotNetNuke issue.
Debugging shows the controller firing and retrieving a record but the form items are never populated.
I've tried both a jquery.post and an .ajax call. Neither work.
View:
#inherits DotNetNuke.Web.Mvc.Framework.DnnWebViewPage<IMS.Model.lh.Associations>
#using IMT.LH.AssociationAdmin.Models
#using IMS.Model.lh
#{
ViewBag.Title = "Association";
}
<h2>Association</h2>
<div id="Associations-#Dnn.ModuleContext.ModuleId">
#Html.DropDownList("SelectAssociation")
</div>
<div id="Association-#Dnn.ModuleContext.ModuleId">'
#Html.LabelFor(m=>m.AssociationID)<br />
#Html.Label("Title")<br />
#Html.Label("Slug")<br />
#Html.Label("Description")<br />
#Html.Label("Tagline")<br/>
</div>
<script type="text/javascript">
const rvtoken = $("input[name='__RequestVerificationToken']").val();
const moduleId = #Dnn.ModuleContext.ModuleId;
const tabId = #Dnn.ModuleContext.TabId;
$.ajaxSetup({
headers: {
"RequestVerificationToken": rvtoken
}
});
$('#SelectAssociation').change(
function() {
$.post(
"/DesktopModules/MVC/LH.AssociationAdmin/Association/SelectAssociation",
{
id: $(this).val()
}),
function() {
alert("success");
}
/*$.ajax({
url: "/DesktopModules/MVC/LH.AssociationAdmin/Association/SelectAssociation",
method: "Post",
data: {
id: $(this).val()
},
headers: {
"ModuleId": moduleId,
"TabId": tabId,
"RequestVerificationToken": rvtoken
},
success: function() {
alert("success");
},
fail: function() {
alert("fail");
}
});
*/
});
</script>
Controller:
[HttpPost]
[DotNetNuke.Web.Mvc.Framework.ActionFilters.ValidateAntiForgeryToken]
public ActionResult SelectAssociation()
{
ModelState.Clear();
int id = Convert.ToInt32(Request.Form["id"]);
using (var ac = new AssociationsController())
{
ac.GetById(id);
return PartialView("Index", ac.CurrentRec);
}
}
Ok. One way I've found is to simply return a json object to the .ajax call and parse it:
$.ajax({
cache: false,
dataType: 'json',
url: "/DesktopModules/MVC/LH.AssociationAdmin/Association/SelectAssociation",
method: "Post",
data: {
id: $(this).val()
},
headers: {
"ModuleId": moduleId,
"TabId": tabId,
"RequestVerificationToken": rvtoken
},
success: function(data) {
//alert(data.AssociationID);
$('#AssociationID').text(data.AssociationID);
$('#Title').text(data.Title);
$('#Slug').text(data.Slug);
$('#Description').text(data.Description);
$('#Tagline').text(data.Tagline);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(textStatus);
console.log(errorThrown);
console.log(jqXHR);
}
});
});
Controller:
[HttpPost]
[DotNetNuke.Web.Mvc.Framework.ActionFilters.ValidateAntiForgeryToken]
public ActionResult SelectAssociation()
{
ModelState.Clear();
int id = Convert.ToInt32(Request.Form["id"]);
using (var ac = new AssociationsController())
{
ac.GetById(id);
return Json(ac.CurrentRec);
//return PartialView("Index", ac.CurrentRec);
}
}
But, I am certainly open to other suggestions.
I've a calendar that I want to list various types of event on and enable a checkbox filter to show/hide those kind of events.
Is there a way to say on this action, ONLY load from 1 data-source AND remember that URL on month next/prev links?
I've started using eventSources, but it loads them all, rather than the one(s) I want.. Here's what I have.
var fcSources = {
all: {
url: tournament_url + '/getCalendar/?typefilter=[1,2]'
},
type1: {
url: tournament_url + '/getCalendar/?typefilter=[1]'
},
type2: {
url: tournament_url + '/getCalendar/?typefilter=[2]'
}
};
These URLS all provide a json string of events, based on the types prrovided.
Here's my calendar:
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek'
},
firstDay: 1, // Monday = 1
defaultDate: new Date(), // Now
editable: true,
eventSources: [ fcSources.all ],
events: fcSources.all,
nextDayThreshold: '00:00:00',
... etc etc
Above my calendar I have this:
input type="checkbox" name="event_filter[]" value="type1" /> Type 1
input type="checkbox" name="event_filter[]" value="type2" /> Type 2
And finally , two Jquery fucntions.. one to get all the filters:
function getFilters(getName) {
var filters = [];
var checked = [];
$("input[name='"+getName+"[]']").each(function () {
filters.push( $(this).val() );
});
$("input[name='"+getName+"[]']:checked").each(function () {
checked.push( $(this).val() );
});
return [filters, checked];
}
and the last to load them:
$(".event_filter").on('click', function() {
var doFilters = getFilters('event_filter');
$('#calendar').fullCalendar( 'removeEvents' );
if (doFilters[0] === doFilters[1]) {
$('#calendar').fullCalendar( 'addEventSource', fcSources.all );
} else {
$.each(doFilters[1], function(myFilter, myVal) {
console.log(myVal);
$('#calendar').fullCalendar( 'addEventSource', fcSources.myVal );
});
}
// $('#calendar').fullCalendar( 'refetchEvents' );
});
Since the sources are all the same place and just different data on the URL, this approach could meet your needs. In the demo it just alerts the URL that is being tried but doesn't actually supply any data...
https://jsfiddle.net/gzbrc2h6/1/
var tournament_url = 'https://www.example.com/'
$('#calendar').fullCalendar({
events: {
url: tournament_url + '/getCalendar/',
data: function() {
var vals = [];
// Are the [ and ] needed in the url? If not, remove them here
// This could (should!) also be set to all input[name='event_filter[]'] val's instead of hard-coded...
var filterVal = '[1,2]';
$('input[name="event_filter[]"]:checked').each(function() {
vals.push($(this).val());
});
if (vals.length) {
filterVal = '[' + vals.join(',') + ']' // Are the [ and ] needed in the url? If not, remove here too
}
return {
typefilter: filterVal
};
},
beforeSend: function(jqXHR, settings) {
alert(unescape(settings.url));
}
}
});
// when they change the checkboxes, refresh calendar
$('input[name="event_filter[]"]').on('change', function() {
$('#calendar').fullCalendar('refetchEvents');
});
Try this!
$('#calendar').fullCalendar({
events: function( start, end, timezone, callback ) {
var checked = [];
$("input[name='event_filter[]']:checked").each(function () {
checked.push( $(this).val() );
});
var tournament_url = 'https://www.example.com';
$.ajax({
url: tournament_url + '/getCalendar/',
data: {typefilter: '['+checked.join(',')+']'},
success: function(events) {
callback(events);
}
});
}
});
$('input[name="event_filter[]"]').on('change', function() {
$('#calendar').fullCalendar('refetchEvents');
});
This works for me.
I'm really struggling to understand what's going on here. I have a template
<template name="gallery">
<div>
123
</div>
{{#each art}}
{{name}}
{{/each}}
</template>
Then I have routes like this
Router.map(function() {
this.route('/', {
template: 'gallery',
data: function() {
var page = 1;
return {
page: page,
};
},
});
this.route('/gallery/:_page', {
template: 'gallery',
data: function() {
var page = parseInt(this.params._page);
return {
page: page,
};
},
});
});
Following this article I'm using template subscriptions like this
var G_PAGE_SIZE = 10;
Template.gallery.onCreated(function() {
var instance = this;
var route = Router.current();
instance.loaded = new ReactiveVar(0);
instance.autorun(function() {
var pageId = parseInt(route.params._page);
var page = pageId - 1;
var skip = page * G_PAGE_SIZE;
var subscription = instance.subscribe('artForGrid', skip, G_PAGE_SIZE);
if (subscription.ready()) {
instance.loaded.set(1);
}
});
instance.art = function() {
return Art.find({});
};
});
Template.gallery.helpers({
art: function() {
return Template.instance().art();
},
});
It works but when I click one of the links changing the route the page doesn't re-render
So ... some other answer on here said that's because there's no reactive connection between data changing on the route through the router and the template.
So I tried use route.state which as a ReactiveDict (not sure where that came from). If it's part of iron:router or if it's part of reactive-var
Anyway I changed the code to
Router.map(function() {
this.route('/', {
template: 'gallery',
data: function() {
var page = 1;
this.state.set('page', page); // <------------ADDED
return {
page: page,
};
},
});
this.route('/gallery/:_page', {
template: 'gallery',
data: function() {
var page = parseInt(this.params._page);
this.state.set('page', page); // <------------ADDED
return {
page: page,
};
},
});
});
In the onCreated
Template.gallery.onCreated(function() {
var instance = this;
var route = Router.current();
instance.loaded = new ReactiveVar(0);
instance.autorun(function() {
var pageId = route.state.get('page'); // <--------- CHANGED
var page = pageId - 1;
var skip = page * G_PAGE_SIZE;
var subscription = instance.subscribe('artForGrid', skip, G_PAGE_SIZE);
if (subscription.ready()) {
instance.loaded.set(1);
}
});
instance.art = function() {
return Art.find({});
};
});
Template.gallery.helpers({
art: function() {
return Template.instance().art();
},
});
That doesn't work either except sometimes. In the interest of debugging I added a console.log to the route
Router.map(function() {
this.route('/', {
template: 'gallery',
data: function() {
var page = 1;
this.state.set('page', page);
console.log("/root"); // <------------ADDED
return {
page: page,
};
},
});
this.route('/gallery/:_page', {
template: 'gallery',
data: function() {
var page = parseInt(this.params._page);
this.state.set('page', page);
console.log("/gallery/" + page); // <------------ADDED
return {
page: page,
};
},
});
});
Suddenly it starts working !???!#!? I remove the console.logs and it stops
I also tried adding actions without the console.logs
Router.map(function() {
this.route('/', {
template: 'gallery',
data: function() {
var page = 1;
this.state.set('page', page);
return {
page: page,
};
},
action: function() { // <- added
var page = 1; // <- added
this.state.set('page', page); // <- added
this.render(); // <- added
}, // <- added
});
this.route('/gallery/:_page', {
template: 'gallery',
data: function() {
var page = parseInt(this.params._page);
this.state.set('page', page);
return {
page: page,
};
},
action: function() { // <- added
var page = parseInt(this.params._page); // <- added
this.state.set('page', page); // <- added
this.render(); // <- added
}, // <- added
});
});
That doesn't work either.
update
So using Router.current().data().??? seems to make it work. It's now this
Router.map(function() {
this.route('/', {
template: 'gallery',
data: function() {
var page = 1;
// <--- removed this.state() stuff
return {
page: page,
};
},
// <---- removed action stuff
});
this.route('/gallery/:_page', {
template: 'gallery',
data: function() {
var page = parseInt(this.params._page);
// <--- removed this.state() stuff
return {
page: page,
};
},
// <---- removed action stuff
});
});
helper
Template.gallery.onCreated(function() {
var instance = this;
instance.loaded = new ReactiveVar(0);
instance.autorun(function() {
var route = Router.current(); // <----- Moved from 3 lines up
var pageId = route.data().page; // <--------- CHANGED
var page = pageId - 1;
var skip = page * G_PAGE_SIZE;
var subscription = instance.subscribe('artForGrid', skip, G_PAGE_SIZE);
if (subscription.ready()) {
instance.loaded.set(1);
}
});
instance.art = function() {
return Art.find({});
};
});
Template.gallery.helpers({
art: function() {
return Template.instance().art();
},
});
No idea if this is now correct or if I'm just getting lucky like with the console.log editions
My guess is that your autorun function is not being re-run because you are not using any reactive variables. I'm not sure if Router.current() or Router.current().data() are reactive, but if they're not, then this explains the issue clearly for me. To test this, try putting some console logging in your autorun function.
Now, I would proposed some revisions that may work for you.
Routing code
Redirect to '/' URL to '/gallery/1' so you can re-use routing code
Router.route('/', function(){
this.redirect('/gallery/1');
});
Sending the page number in the data object to your template is important so that we can use this object in our autorun function
Router.route('/gallery/:_page', {
template: 'gallery',
data: function() {
return {
page: this.params._page,
};
}
});
Also, you don't need to use parseInt method unless you are trying to re-route or handle that exception in your routing code.
Gallery onCreated and helpers
There is a Template.subscriptionsReady helper provided by Meteor so you don't have to manually check if the subscription is loaded unless you really want it. Here's the Template.subscriptionsReady documentation
Here's a simplified version of your onCreated method using the template.data object which was built by the routing code above. Using the autorun wrapper is important so that it will re-run when template.data is changed by your router.
var G_PAGE_SIZE = 10;
Template.gallery.onCreated(function() {
var instance = this;
instance.autorun(function() {
// access the data object built by your route code
var pageId = instance.data.page;
var page = pageId - 1;
var skip = page * G_PAGE_SIZE;
instance.subscribe('artForGrid', skip, G_PAGE_SIZE);
});
});
Define access to your reactive data sources using the template.helpers method.
Template.gallery.helpers({
art: function() {
return Art.find({});
}
});
Note: Not necessary to define your helper in the onCreated and template.helpers
Can anyone tell me why async: false; doesn't allow my spinner to display during data load?
But when I used async: true; it reverses my default display toggle?
I currently have list set up with 5 different items. 3 of those items are set up to display by default when the page loads. Then they can be clicked off or the other 2 items can be clicked on. When I use async: true; to get my spinner to display, its like it doesn't recognize the default toggles. Even though they are selected on page load, the information does not display. It only displays after they are UNselected.
$(function () {
var HistoryViewModel;
var xspinner = new Spinner();
$.ajax({
url: '/Users/GetAccountHistory',
type: 'POST',
async: false,
cache: false,
data: { accountId: #Model.Account.AccountID },
beforeSend: function () {
xspinner.showSpinner("Getting History Information...");
},
complete: function () {
xspinner.hide();
},
success: function (data) {
HistoryViewModel = new RCM.ViewModels.History.HistoryViewModel(ko.mapping.fromJS(data));
ko.applyBindings(HistoryViewModel, document.getElementById('ListPanel'));
ko.applyBindings(HistoryViewModel, document.getElementById("DetailsPanel"));
ie9HeightFix();
},
error: function (xhr, textStatus, errorThrown) {
notify.show({ type: 'error', header: 'Error!', content: 'Error fetching History, ' + xhr.statusText });
return false;
}
});
function ie9HeightFix() {
if(document.addEventListener && !window.requestAnimationFrame)
{
var eTop = $('.AccountHx').offset().top; //get the offset top of the element
var dTop = eTop - $(window).scrollTop(); //position of the ele w.r.t window
var cHeight = $(window).height() - dTop - $(".colophon").height();
$(".AccountHx").css("height", cHeight);
$(".AccountHx").css("position", "relative");
$(".AccountHx").css("overflow-x", "hidden");
$(".AccountHx-detailsPanel").css("left", "32rem");
$(".viewDetailsButton").css("right", "2rem");
}
}
$(window).resize(function() {
ie9HeightFix();
});
$("#vToggle").click();
$("#pToggle").click();
$("#sToggle").click();
$(".search-button").click(function(event){
$(".subHeader").toggleClass("search-visible");
$(".AccountHx-items").toggleClass("search-visible");
$(".search-button").toggleClass("search-visible");
});
$(".filter-button, .modal-close").click(function(event){
$(".AccountHx-filtersPanel").toggleClass("filters-visible");
$(".filter-birdBeak").toggle();
});
$(".filterButtons input").attr("disabled", false);
});
$("#vToggle").click(function(){
$(".js-vItem").toggle();
$("#vToggle").toggleClass("is-active");
});
$("#cToggle").click(function(){
$(".js-claimItem").toggle();
$("#cToggle").toggleClass("is-active");
});
$("#nToggle").click(function(){
$(".js-nItem").toggle();
$("#nToggle").toggleClass("is-active");
});
$("#sToggle").click(function(){
$(".js-sItem").toggle();
$("#sToggle").toggleClass("is-active");
});
$("#pToggle").click(function(){
$(".js-pItem").toggle();
$("#pToggle").toggleClass("is-active");
});
$("li.filterButton").click(function(){
$(this).parent().siblings(".clearButton").show();
$(".AccountHx-filtersPanel .clearButton--all").css('display', 'inline-block');
});
$(".clearButton").click(function(){
var hiddenOption = $(this).siblings(".filterButtons").children(".hiddenOption");
hiddenOption.siblings('.is-active').removeClass('is-active');
hiddenOption.toggleClass("is-active");
$(this).hide();
});
$(".AccountHx-filtersPanel .clearButton--all").click(function() {
$(".AccountHx-filtersPanel .filterButton").removeClass("is-active");
$(".AccountHx-filtersPanel .hiddenOption").toggleClass("is-active");
$(".AccountHx-filtersPanel .filterCheckbox").attr('checked', false);
$(".AccountHx-filtersPanel .clearButton").hide();
$(this).hide();
})
The answer that I discovered is to move the
$("#visitToggle").click();
$("#paymentToggle").click();
$("#statementToggle").click();
inside the following section
success: function (data) {...}
and change async to true
Hello I have the following code which uses knockout.js,
var allJobberDetailsArray = [];
getAllJobberDetailsArray();
function getAllJobberDetailsArray() {
$(function() {
$.ajax({
url: 'http://example.com',
type: 'GET',
data: {
request: "yes",
getAllJobberDetailsArray: "yes"
},
success: function(data) {
allJobberDetailsArray = data;
}
});
})
}
// ViewModel
function JobberCheckBoxListUserControlViewModel() {
var self = this;
self.allJobberDetailsArray = ko.observableArray(allJobberDetailsArray);
}
ko.applyBindings(new JobberCheckBoxListUserControlViewModel());
any change to allJobberDetailsArray I would like to update the UI automatically say an array item is added updated or deleted i would like the UI to reflect it.
How do i achieve it?
EDIT
var allJobberDetailsArray = ko.observableArray();
getAllJobberDetailsArray();
function getAllJobberDetailsArray() {
$(function() {
$.ajax({
url: 'http://example.com',
type: 'GET',
data: {
request: "yes",
getAllJobberDetailsArray: "yes"
},
success: function(data) {
allJobberDetailsArray.removeAll();
allJobberDetailsArray.push(data);
},
});
})
}
// ViewModel
function JobberCheckBoxListUserControlViewModel() {
var self = this;
self.allJobberDetailsArray = allJobberDetailsArray;// allJobberDetailsArray is now observable but any change to this doesn't reflect in the UI
}
var viewModel = new JobberCheckBoxListUserControlViewModel();
ko.applyBindings(viewModel);
in the above code i made the array itself observable and trying to auto update the UI if there are any changes to the array how do i achieve it?
Firstly the array you are updating isn't an observable array. Secondly your success callback actually overwrites the array. Finally you do not keep a reference to your viewModel so you are unable to properly update the observable array. ObservableArray wrap real arrays to make them observable. All add's removes etc need to go through the observableArray so the subscribers can be notified.
Try this instead.
// ViewModel
function JobberCheckBoxListUserControlViewModel(initialArray) {
var self = this;
self.allJobberDetailsArray = ko.observableArray(initialArray || []);
}
var viewModel = new JobberCheckBoxListUserControlViewModel();
function getAllJobberDetailsArray() {
$(function() {
$.ajax({
url: 'http://example.com',
type: 'GET',
data: {
request: "yes",
getAllJobberDetailsArray: "yes"
},
success: function(data) {
viewModel.allJobberDetailsArray(data);
}
});
})
}
ko.applyBindings(viewModel);
I would also recommend refactoring to put the getAllJobberDetailsArray method inside your viewModel so it doesn't polute the global namespace and maintains encapsulation.
Hope this helps.