I have two sections: combinedCriteria and filteredServices. These tow sections are connected through knockoutjs script, when I hit click on any item in filteredService section, it adds that service in above section called combinedCriteria:
<section class="combine-list-container" data-bind="visible: combineSchedules()">
<ul>
<!-- ko foreach: combinedCriteria -->
<li>
<span class="icon-delete" data-bind="click: $parent.deleteCombinedSelection"></span>
<span data-bind="text: service.Name"></span>
<span><span class="min emp" data-bind="text: service.Duration, visible: false"></span></span>
</li>
<!-- /ko -->
</ul>
</section>
<section data-bind="visible: isServiceSectionVisible">
<!-- ko foreach: filteredSerivces -->
<header>
<span data-bind="text: ServiceTypeName"></span>
</header>
<ul data-bind="foreach: GroupedServices">
<li style="height:100%;" class="service">
</li>
</ul>
<!-- /ko -->
</section>
What I want to do, is when user clicks on some service in filtered services section, as it works now, to add it in above section combinedCriteria, but to show short effect with some background graying, and then back as it was.
function setServiceId(serviceId) {
var helperId = serviceId;
vm.selectedServiceId('');
vm.selectedServiceId(helperId);
vm.selectedServiceId(serviceId);
}
serviceIdSubscrier = vm.selectedServiceId.supsendableSubscribe(changeServiceId);
function changeServiceId() {
var currentService = getService();
if (vm.combineSchedules()) {
var isShownMessage = null;
if (vm.combinedCriteria().length > 4 && isShownMessage != true) {
var isShownMessage = true;
if (isShownMessage) {
var style = getDialogStyle();
theDialog = dialog.showMessage(vm.moreThen5SchedulesMessage(), ' ', [shell.cancelButtonText()], false, {
style: style
}).then(function (dialogResult) {
closeDialogForMoreThan5();
isShownMessage = false;
});
}
}
else {
vm.selectedService(currentService || {});
refreshAllowedTimes().then(function () {
setTimeByPreviousSelection();
checkToPushCriteria();
});
}
} else {
refreshOnServiceType();
}
}
function checkToPushCriteria() {
//if (vm.combinedCriteria().length > 4) {
// var style = getDialogStyle();
// theDialog = dialog.showMessage(vm.moreThen5SchedulesMessage(), ' ', [shell.cancelButtonText()], false, {
// style: style
// }).then(function (dialogResult) {
// closeDialogForMoreThan5();
// });
//}
//else {
if (vm.selectedService().Id) {
vm.combinedCriteria.push({
service: vm.selectedService() || {}
});
if (vm.combineSchedules() == 1) {
withSuspendition(employeeIdSubscriber, function () {
vm.employeeId('');
});
}
vm.selectedService({});
refreshCurrentDate();
}
//}
}
so basically, in the function called checkToPushCriteria() I need to catch event when it adds to an array: vm.combinedCriteria.push({service: vm.selectedService() || {}});
I would probabaly add something like jQuery(".someclass").css('background', 'red'); But I dont know which class is it (unkown identifier), also I dont know how to put highlight background color for some period of time (for example 0.5 seconds)
The foreach binding has a few callbacks you can use for exactly this purpose, specifically: afterRender and afterAdd. This piece of documentation should probably help you along.
Here's a small example: https://jsfiddle.net/thebluenile/9qarun4o/
Note that it's 2021 and there are nicer ways to make elements flash than using jQuery, such as CSS animations. But sometimes, you just gotta work with what you know/have...
Related
I'm trying to filter different posts kinda page. Each post has many different categories. But the search filter is two-layered, the main filter and then below a more specific selection of filters where I used checkboxes. The problem is that all categories are on the same level. How can I access each selected class based on the user filter input and then output the right post?
Categories and their classes are listed like this:
<span class="category">
<span class="cat Event">Event</span>
<span class="cat Developing">Developing</span>
<span class="cat SQL">SQL</span>
</span>
Where "Event" is in the main filter and the other two are in the second checkbox filter
The project is done in MVC .NET and for filtering functionality, I'm using jQuery
This is how to get each post in my view:
<div class="novice-list">
#foreach (var item in Model.Articles)
{
<div class="novica-container">
<a href="#DataContext.Current.RouteUrlManager.GetModuleLink("article", null, "details", item.Id, item.Title)">
<div class="media">
#{
string slika = string.Empty;
if (item.Id > 166 || item.Id == 159)
{
slika = $"{WellKnownStrings.StorageBaseUrl}{WellKnownStrings.ArticleImageContainer}/{item.FeaturedImage}";
}
else
{
slika = item.FeaturedImageUrl;
}
}
<div class="slika" style="background-image: url('#if (item.FeaturedImageUrl != "") { #slika }');">
</div>
<div class="media-body">
#*content*#
<div class="meta">
<span class="published">#item.DateFormated</span>
<span class="category">
#foreach (var cat in #item.Category)
{
<span class="cat #cat.Title">#cat.Title</span>
}
</span>
</div>
<h2>#item.Title</h2>
<p>#item.Summary</p>
</div>
</div>
</a>
</div>
}
</div>
this is how I get matches from first (Main) filtering:
isCheckedMain = true;
if (isCheckedMain) {
var selectedClass = $(this).attr('class');
// reset the active class on all the buttons
$('#filterOptions li').removeClass('show');
// update the active state on our clicked button
$(this).parent().addClass('show');
if (selectedClass == 'all') {
// show all our items
$('.novica-container').slideDown(1000, "linear");
}
else {
// hide all elements that don't share ourClass
$('.novica-container').hide();
// show all elements that do share ourClass
$('.novica-container').find('span.cat.' + selectedClass).parents('.novica-container').slideDown(1000, "linear");
}
}
And matches for checkbox filter:
$(".checkbox-filter :checkbox").click(function () {
isBoxChecked = true;
if (isCheckedMain && isBoxChecked) {
var selectedBox = $(this).attr('id');
var selectedMain = selectedClass;
if ($('input#' + selectedBox).is(':checked')) {
if ($('span.cat').hasClass(selectedMain)) {
$('.novica-container').hide();
$('span.cat.'+selectedMain).addClass(selectedBox);
$('.checkbox-filter :checkbox:checked').each(function () {
//Get the selected checkbox value
var selectedBox = $(this).attr('id');
$('.novica-container').find('span.cat.' + selectedMain ,'.'+selectedBox).parents('.novica-container').slideDown(700);
});
}
}
else if ($(this).is(':not(checked')) {
$('.novica-container').find('span.cat.' + selectedBox).parents('.novica-container').slideUp(700);
if (($('input[type="checkbox"]:checked').length === 0)) {
isBoxChecked = false;
$('.novica-container').slideDown(1000, "linear");
This is what I have so far, I'm trying to do something with true-false( if one is true, search only with the main filter, if both are true search more accurately with other categories. And I've been stuck here for days trying to merge both sides of the code. They both work separately but not together
Picture of filter
Found a solution for this.
I added all of the categories names one level higher in my View. So that class="category" now has values of all the other categories.
Example: class="category Event Developing SQL"
And then I could just use this jQuery to filter selected posts
$('span.category.' + selectedMain + '.' + selectedBox).parents('.novica-container').slideDown(700);
Sorry if title confuses. Hard to think of how to describe the problem in few words.
I have 3 different lists containing "level", "category", "time" for the game. Originally none of the lists' items have any class. When selected an item from each list, it gets a class 'activeSelection'.
I need to check if in each of the lists there is an item that has that class. If not, then show the warning under the list (there is a warning for each list) that at the moment is hidden. Also not let "startGame()" function to execute.
I need it to work so if 2 lists are missing that class, the warning would be shown under both of those lists. The same if all 3 lists are missing the class.
const levelList = document.querySelector('.levelList');
const categoryList = document.querySelector('.categoryList');
const timeList = document.querySelector('.timeList');
$(document).ready(function() {
$(playButton).on('click', function() {
if ($(levelList).children("li").hasClass('activeSelection') == false) {
$(levelList).find('.warning').show();
} else {
startGame();
} /** that's how I check one of the lists. I need to check all 3
and if at least one does not have a class give a warning, but not
stop checking until all 3 lists are checked to give the rest of
the warnings if needed **/
});
});
.warning {
display: none;
}
<div class="list">
<div class="levelList">
<h2>level</h2>
<ul>
<li>easy</li>
<li>medium</li>
<li>hard</li>
</ul>
<p class="warning">! please select level !</p>
</div>
<div class="categoryList">
<h2> category</h2>
<ul>
<li>movies</li>
<li>songs</li>
<li>people</li>
<li>animals</li>
<li>random things</li>
</ul>
<p class="warning">! please select Category !</p>
</div>
<div class="timeList">
<h2>time</h2>
<ul>
<li>1 min</li>
<li>2 min</li>
<li>3 min</li>
</ul>
<p class="warning">! please select time !</p>
</div>
</div>
You can try doing all 3 checks separately:
$(document).ready(function() {
$(playButton).on('click', function() {
var status = 0;
if ($(levelList).children("li").hasClass('activeSelection') == false) {
$(levelList).find('.warning').show();
} else {
status += 1;
}
if ($(categoryList).children("li").hasClass('activeSelection') == false) {
$(categoryList).find('.warning').show();
} else {
status += 1;
}
if ($(timeList).children("li").hasClass('activeSelection') == false) {
$(timeList).find('.warning').show();
} else {
status += 1;
}
if (status === 3)
startGame();
});
I have a script that produces an array of forms, with each form affecting the available options for the next form. The awesome martin booth solved the problem of getting the displayed values to update as new forms are added.
however, I have an observable array (defaultSampleRates) that sits outside the forms array, and for the life of me I can't get the form to push items into that array. I've tried declaring it in a dozen different places in a dozen different ways, but it just won't stick.
basically I need the 'Default sample rate' drop-down to show the sample rates that have been selected in the form above (the user must only be able to choose a default sample rate from a displayed one, rather than from the full list).
Any tips much helpo brain pain. fiddle here: http://jsfiddle.net/3lliot/9vsa4hh7/
html:
<body>
<div style="float:left; width:60%">
<div data-bind="foreach: forms">
<div style="float:left; margin-right:20px"> <span>
<!-- This is a *view* - HTML markup that defines the appearance of your UI -->
<p><span style="color:#AB0002">Sample rate element <span data-bind="text: formNum"></span></span>
</p>
<p>Sample rate (Hz):
<select data-bind="options: sampleRates, value: selectedSampleRate"></select>
</p>
</span>
</div>
</div>
<div style="float:left; clear:both; margin-bottom:20px">
<hr/>
<button data-bind="click: addForm">Add <srate> element</button>
<button data-bind="click: removeForm">Remove</button>
<p>Default sample rate:
<select data-bind="options: defaultSampleRates, value: selectedDefaultSampleRate"></select>
</p>
</div>
</div>
<div style="float:right; width:38%; overflow:scroll; border-left:thin; border-left-style:solid; border-left-color:#dfdfdf;padding-left: 1%"> <span class="code"><audio></span>
<ul data-bind="foreach: forms">
<li>
<!-- render the json --> <span class="code"> <srate id="<span data-bind="text: formNum"></span>">
<br/> <sample_rate><span data-bind="text: selectedSampleRate"></span></sample_rate>
<br/> </srate></span>
</li>
</ul> <span class="code"> <default_srate><span data-bind="text: selectedDefaultSampleRate"></span></default_srate></span>
<br/><span class="code"></audio></span>
</div>
</body>
js:
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
//window.onload = startKnockout;
window.onload = startKnockout;
var formNum;
var i = -1;
var selectedSampleRates = [];
function Form(allSampleRates, forms) {
var self = this;
// Declare observables
self.selectedSampleRate = ko.observable();
self.formNum = ko.observable();
self.sampleRates = ko.computed(function () {
var formsValue = forms(),
availableSampleRates = ko.utils.arrayFilter(allSampleRates, function (sampleRate) {
return !ko.utils.arrayFirst(formsValue, function (form) {
if (form != self) {
if (form.selectedSampleRate() === sampleRate) {
if (selectedSampleRates.indexOf(sampleRate) === -1) {
selectedSampleRates.push(sampleRate);
}
}
return form.selectedSampleRate() === sampleRate;
} else {
return form != self;
}
});
});
return availableSampleRates;
});
// count how many srate elements there are
i++;
self.formNum = i;
}
var Vm = function () {
var self = this;
var item = 0,
allSampleRates = ['192000', '176400', '96000', '88200', '48000', '44100'];
// declare observables for options outside the srate elements
self.selectedDefaultSampleRate = ko.observable();
// add remove forms stuff
self.forms = ko.observableArray([]);
self.forms.push(new Form(allSampleRates, self.forms));
item++;
self.addForm = function () {
if (i < 5) {
self.forms.push(new Form(allSampleRates, self.forms));
item++;
} else {
alert("Can't have more than 6 <srate> elements!")
}
};
self.removeForm = function () {
if (item > 1) {
self.forms.splice(item - 1, 1);
item--;
i--;
} else {
alert("Must have at least one <srate> element!")
}
};
// define arrays for options outside srate elements
self.defaultSampleRates = ko.observableArray([]);
return self;
}
// Activates knockout.js
function startKnockout() {
ko.applyBindings(new Vm());
};
You can make use of selectedOptions binding to add defaultSample rate.
I changed select sampleRates code to this
<select data-bind="options: sampleRates, value: selectedSampleRate, selectedOptions: $root.defaultSampleRates"></select>
Notice selectedOptions binding there..
Should work as per your need.
Updated Fiddle Demo here : http://jsfiddle.net/rahulrulez/9vsa4hh7/3/
I hope that's what you wanted.
I have a little knockout form which I am able to duplicate using a button to allow multiple sets of data to be provided.
However, each value for the 'sample rate' select can only be used once. For example, the first form by default is set to 192000. So when I click 'add srate element' the form that is generated should not include 192000 as an option in the Sample rate drop-down.
Obviously if the Sample rate for the first form is set to something else, that value should be removed for the second form, and so on.
How do I remove the already-selected Sample rate from the array when a new form is added, so that it is not available in further copies of the form? It is even possible, given the structure of the view model?
There's a fiddle here: http://jsfiddle.net/3lliot/x3cg131g/
There's a bit of logic included already to prevent more than 6 forms.
Any tips will be appreciated ...
Html code:
<body>
<ul data-bind="foreach: forms">
<li>
<!-- This is a *view* - HTML markup that defines the appearance of your UI -->
<p><span style="color:#AB0002">Sample rate element <span data-bind="text: formNum"></span></span>
</p>
<p>Sample rate (Hz):
<select data-bind="options: $parent.sampleRate, value: selectedSampleRate"></select>
</p>
<p>TDM channels per line:
<select data-bind="options: tdmChans, value: selectedTdmchan"></select>
</p>
</li>
</ul>
<button data-bind="click: addForm">Add <srate> element</button>
<button data-bind="click: removeForm">Remove</button>
<hr/>
<ul data-bind="foreach: forms">
<li>
<!-- render the json -->
<p class="code"><srate id="<span data-bind="text: formNum"></span>">
<br/> <sample_rate><span data-bind="text: selectedSampleRate"></span></sample_rate>
<br/> <tdm_chan><span data-bind="text: selectedTdmchan"></span></tdm_chan>
<br/>
</p>
</li>
</ul>
</body>
JS code:
window.onload = startKnockout;
var formNum;
var i = -1;
function Form() {
var self = this;
// Declare observables
self.selectedSampleRate = ko.observable();
self.selectedTdmchan = ko.observable();
self.formNum = ko.observable();
// Define controls
self.tdmChans = ko.computed(function() {
if (self.selectedSampleRate() == 44100 || self.selectedSampleRate() == 48000) {
return ['2', '4', '8', '16'];
} else if (self.selectedSampleRate() == 88200 || self.selectedSampleRate() == 96000) {
return ['2', '4', '8'];
} else if (self.selectedSampleRate() == 176400 || self.selectedSampleRate() == 192000) {
return ['2', '4'];
} else {
// do nothing
}
}, self);
i++;
self.formNum = i;
}
var Vm = function() {
var self = this;
var item = 0;
self.forms = ko.observableArray([]);
self.forms.push(new Form());
item++;
self.addForm = function() {
if (i < 5) {
self.forms.push(new Form());
item++;
} else {
alert("Can't have more than 6 <srate> elements!")
}
};
self.removeForm = function() {
if (item > 1) {
self.forms.splice(item - 1, 1);
item--;
i--;
} else {
alert("Must have at least one <srate> element!")
}
};
self.sampleRate = ko.observableArray(['192000', '176400', '96000', '88200', '48000', '44100']);
return self;
}
// Activates knockout.js
function startKnockout() {
ko.applyBindings(new Vm());
};
Take a look at this:
http://jsfiddle.net/martinbooth/x3cg131g/1/
importantly, compute the available samples rates based on what has been selected in other forms:
self.sampleRates = ko.computed(function(){
var formsValue = forms(),
availableSampleRates = ko.utils.arrayFilter(allSampleRates, function(sampleRate){
return !ko.utils.arrayFirst(formsValue, function(form){
return form != self && form.selectedSampleRate() === sampleRate;
});
});
return availableSampleRates;
});
In my view I am looping through an observableArray (itemGroup) that has one property that is also an observableArray (item). I have a method to remove an entire itemGroup and one to remove an item from and itemGroup but I would like to add in some logic along the lines of it there is only 1 item left in the group removing that item should also remove the itemGroup.
here is an example of the relevant parts of my view model and view.
my JS
var ItemModel = function(item) {
var self = this;
self.name = ko.observable(item.name);
self.price = ko.observable(item.price);
};
var ItemGroupModel = function(itemGroup) {
var self = this;
self.order = ko.observable(itemGroup.order);
self.items = ko.observableArray(ko.utils.arrayMap(itemGroup.items, function(item){
return new ItemModel(item);
}));
self.type = ko.observable(item.type);
self.removeItem = function(item) {
self.items.remove(item);
}
};
var ViewModel = function(data) {
var self = this;
self.itemGroups = ko.observableArray(ko.utils.arrayMap(data.itemGroups, function(itemGroup) {
return new ItemGroupModel(item);
}));
// some other properties and methods
self.removeItemGroup = function(itemGroup) {
self.itemGroups.remove(itemGroup);
}
};
My View
<ul data-bind="foreach: {data: VM.itemGroups, as: 'itemGroup'}">
<li>
<button data-bind="click: $root.VM.removeItemGroup">X</button>
<ul data-bind="foreach: {data: itemGroup.items, as: 'item'}">
<li>
<!-- ko if: itemGroup.items().length > 1 -->
<button data-bind="click: itemGroup.removeItem">X</button>
<!-- /ko -->
<!-- ko ifnot: itemGroup.items().length > 1 -->
<button data-bind="click: function () { $root.VM.removeItemGroup($parent) }">X</button>
<!-- /ko -->
</li>
</ul>
</li>
</ul>
This works but to me it isnt ideal. It is my understanding that knockout should help me get away from using an anonymous function like "function () { $root.VM.removeItemGroup($parent) }" but I am not sure how to do it another way. Also removing the if and ifnot statements would be good to clean up as well.
I would like to give my solution
send index of itemGroups and items as argument to remove method.
Hope you know how to send index
Then check the length of itemGroups
self.remove(itemGroupsIndex,itemsIndex) {
var itemGroupsLength = self.itemGroups()[itemGroupsIndex].items().length;
if(itemGroupsLength = 1) {
self.itemGroups.remove(itemGroupsIndex);
}
else {
self.itemGroups()[itemGroupsIndex].items.remove(itemsIndex);
}
};