Sorry for maybe uncorrect question, but I feel really confused. I need to set the css class of an item in a foreach loop based on the value of an item's property.
self.CssBind = ko.computed(function (task) {
var CssBind = '';
if (getComplexity(task) === 'Easy') {
CssBind = "green";
} else if (getComplexity(task) === 'Intermediate') {
CssBind = 'yellow';}
else if (getComplexity(task) === 'Difficult') {
CssBind = 'red';
}
return CssBind;
});
I tried to get complexity in such way but have undefined.... (in controller there is method that accepts task and returns complexity)
self.complexity = ko.observable();
function getComplexity (task) {
ajaxHelper(taskItem, 'GET').done(function (data) { self.complexity(data); });
};
In html
<div class="panel panel-default" data-bind="foreach:{data:tasks, as: 'task'}">
<div class="panel-heading">
<a data-toggle="collapse" data-parent="#accordion" data-bind="text: Name, attr: { href : '#collapse' + task.Id}, css: {color: CssBind}">
</a>
</div>
<div data-bind="attr: { id : 'collapse' + task.Id}" class="panel-collapse collapse">
<div class="panel-body">
<span data-bind="text: Name"></span>
</div>
</div>
</div>
What to change to make it work?
Your computed is probably defined on the root view-model and when you're calling it you're already in a foreach: tasks scope. You need to use the $root keyword to point to CssBind.
Also, no need for a computed, regular function will do easier (especially with argument passing):
self.CssBind = function (task) {
var CssBind = '';
if (ko.unwrap(task.getComplexity) === 'Easy') {
CssBind = "green";
} else if (self.getComplexity() === 'Intermediate') {
CssBind = 'yellow';}
else if (self.getComplexity() === 'Difficult') {
CssBind = 'red';
}
return CssBind;
});
And in your HTML:
<a data-toggle="collapse"
data-parent="#accordion"
data-bind="text: Name, attr: { href : '#collapse' + task.Id}, style: {color: $root.CssBind.bind(null, task)}">
Please notice that I change the binding handler from css to style (the former is used to apply CSS classes while the latter applies explicit CSS rules).
Related
I am struggling on adding and removing a style class on a anchor tag. I want to add a active class when I clicked on that tag and remove active from last active one. By default Home page would be active. I have tried many things but no success on that!
I have added few code but it is not working. Below is the code:
angularjs code:
$scope.isActive = false;
$scope.getCSSClass = function () {
return 'active';
}
<div id="mySidenav" class="sidenav">
<span class="whitelogo"><img src="styles/images/logo-small.png" alt="logo"></span>
<div class='clearfix'></div>
×
<a ng-link="['/Home/']" title="Home" class="active"><i class="fas fa-home"></i>Home</a>
<a ng-link="['/TestForm/']" ng-class="getCSSClass()" title="Application Form"><i class="fas fa-user-edit"></i> test Form</a>
</div>
Any help will be appreciated. Thanks in advance!
On Angularjs file, I have added this code:
$scope.getStyleClass = function (path) {
var cur_path = $location.path().substr(0, path.length);
if (cur_path == path) {
if ($location.path().substr(0).length > 1 && path.length == 1)
return "";
else
return "active";
} else {
return "";
}
}
And on Html side:
ng-class="getStyleClass('/TestForm')"
I want to add a class behind payment-method by function with the knockout css binding (in Magento 2.1).
So this is the current code:
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}">
<div class="payment-method-title field choice">
<input type="radio"
name="payment[method]"
class="radio"
data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()"/>
The class is returned by getCode() which works above with the id and value.
So I thought I could do just:
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked()), getCode()}">
But then it fails with:
knockout.js:2624 Uncaught SyntaxError: Unable to parse bindings.
Bindings value: css: {'_active': (getCode() == isChecked()), getCode() }
Message: Unexpected token }
<div class="payment-method" data-bind="css: getCode()">
This works.
<div class="payment-method" data-bind="css: {getCode()}">
This doesn't.
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}, attr: {'class': getCode()}">
This works too but will overwrite the payment-method class and the _active class isn't set either initally anymore.
How do I set that dynamic class?
This piece of code is redundant, as the css data-bind is getting overwrite with your attr binding.
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}, attr: {'class': getCode()}">
This is how you can do your dynamic class (assumption these properties are observable):
<div class="payment-method" data-bind="css: CSS">
self.CSS = ko.computed(function() {
var code = self.getCode();
var isChecked = self.isChecked();
return code + ' ' + (code == isChecked ? '_active' : '');
}, viewModel);
The comment from #tyler_mitchell helped me find the solution myself through this thread: https://stackoverflow.com/a/21528681/928666
I can do:
<div class="payment-method" data-bind="attr: { 'class': 'payment-method ' + getCode() }, css: {'_active': (getCode() == isChecked())}">
Not brilliant but well...
Another example should anyone need it
<div data-bind="attr: { 'class': 'mainClass' + (dynamicClassSetOnce ? ' ' + dynamicClassSetOnce : '' }, css: { 'mainClass--focussed': isFocussed }">
...
</div>
This is my helper for generating a treeview.
Using this i am able to generate the treeview in the mvc5.
#helper GetTreeView(List<MvcTreeview.Models.Category> siteMenu, int parentID)
{
foreach (var i in siteMenu.Where(a => a.ParentID.Equals(parentID)))
{
<li>
#{var submenu = siteMenu.Where(a => a.ParentID.Equals(i.ID)).Count();}
#if (submenu > 0)
{
<span class="collapse collapsible"> </span>
}
else
{
<span style="width:15px; display:inline-block"> </span>
}
<span id="Category">
#i.CategoryName
#*oncontextmenu="return false"*#
</span>
#if (submenu > 0)
{
<ul>
#Treeview.GetTreeView(siteMenu, i.ID)
#* Recursive Call for Populate Sub items here*#
</ul>
}
</li>
}
}
This is my View for displaying
#model List<MvcTreeview.Models.Category>
#{
ViewBag.Title = "Simple";
}
<div class="gridbox gridleft">
<div class="left">
<div style="padding:10px; background-color:#FAFAFA">
<div class="treeview">
#if (Model != null && Model.Count() > 0)
{
<ul>
#Treeview.GetTreeView(Model, Model.FirstOrDefault().ParentID)
</ul>
}
</div>
</div>
</div>
</div>
<div id="onSuccess">
</div>
#* Here We need some Jquery code for make this treeview collapsible *#
#section Scripts{
<script type="text/javascript">
$(document).ready(function () {
$(".treeview li>ul").css('display', 'none'); // Hide all 2-level ul
$(".collapsible").click(function (e) {
e.preventDefault();
$(this).toggleClass("collapse expand");
$(this).closest('li').children('ul').slideToggle();
});
});
function PassingFunction(clicked_id) {
url = '#Url.Action("Details", "TestDetails")';
$.ajax({
url: url,
type: 'GET',
data: { 'id': clicked_id },
success: function (returnData) {
$("#onSuccess").html(returnData);
console.log(returnData);
},
error: {
}
});
}
</script>
}
Now i want to add the custom functionalities to the nodes of the treeview
Add
Delete
Edit
How can i do that?
There is a lot of JQuery Context Menu options on this link.
I have just picked the most forked of them, jQuery contextMenu.
I created a JSFeed fork in an example of treeview using UL/LI and created this JSFiddle with the Context-Menu to help you:
http://jsfiddle.net/mqueirozcorreia/0h82qto6/
Explaining the code:
I have added the externals resources:
http://swisnl.github.io/jQuery-contextMenu/dist/jquery.contextMenu.min.css
https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js
http://swisnl.github.io/jQuery-contextMenu/dist/jquery.contextMenu.min.js
All the magic goes in the javascript code, configuring the context-menu.
The selector property will put the context menu in every element of type <span> and having class attribute with value "contextMenuItem":
selector: 'span.contextMenuItem',
When the user clicks the callback function below runs. In this example, it alerts/logs what key was selected and the id attribute value.
callback: function(key, options) {
var m = "clicked: " + key + " on element of id " + options.$trigger.attr("id");
window.console && console.log(m) || alert(m);
},
In Meteor, I have an app where I make a list of items grouped by tags, where non-tagged items come first and then tagged items are hidden under a "tag header" drop down.
I haven't touched this app since 0.8 came out, and I was using a block helper in a template which worked fine in pre-0.8...
See working jsfiddle here
Handlebars.registerHelper('eachItem', function(context, options) {
var ret = "";
for(var i=0, j=context.length; i<j; i++) {
if(!context[i].tag){
ret = ret + options.fn(context[i]);
} else {
if(i===0||!context[i-1].tag ||context[i-1].tag !== context[i].tag){
ret = ret + '<li class="list-group-item"><a data-toggle="collapse" data-target="#'+ context[i].tag +'"> Items tagged '+context[i].tag + '</a></li><div id="'+context[i].tag+'" class="collapse">';
}
ret = ret + options.fn(context[i]);
if(i+1<j && context[i+1].tag !== context[i].tag){
ret = ret + '</div>';
}
}
}
return ret;
});
But I'm struggling a bit to translate this into post-0.8 Meteor
The inserted HTML must consist of balanced HTML tags. You can't, for example,
insert " </div><div>" to close an existing div and open a new one.
One idea I had was to render the non-tagged items and also the containers in a vanilla {{#each}} loop (with 2 different templates), and then do something like this
Template.myListContainer.rendered = function(){
_.each(tags, function(tag){
var tagged_items = _.filter(items, function(item){ return item.tag == tag; });
_.each(tagged_items, function(item){
UI.insert(UI.RenderWithData(listItemTemplate, { item : item }), tagContainer);
});
});
}
Is there a simpler way to do this ? If the items come from a collection, will they keep their reactivity ?
Many thanks in advance !
If you haven't already, it's worth reading the Meteor wiki on migrating to blaze.
Anyhow, this one seems difficult to implement as a straight iteration.
If you don't need them in a specific order, I would just list items w/ and w/o tags, eg:
Template.example.helpers({
dataWithoutTags: function(){
return items.find({tag:{exists: false}});
},
tagList: function(){
// create a distinct list of tags
return _.uniq(items.find({tag:{exists: true}}, {tag: true}));
},
dataForTag: function(){
// use `valueOf` as `this` is a boxed-string
return items.find({tag: this.valueOf()});
}
});
Template:
<template name="example">
<div class='panel panel-default'>
<ul class='list-group'>
{{#each dataWithoutTags}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
{{#each tagList}}
<li class="list-group-item">
<a data-toggle="collapse" data-target="#{{this}}"> Items tagged {{this}}</a>
</li>
<div id="{{this}}" class="collapse">
{{#each dataForTag}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
</div>
{{/each}}
</ul>
</div>
</template>
If you do need them in a specific order - eg. grouping only consecutive items (as per your example). The only option would be to pre-process them.
eg:
Template.example.helpers({
itemsGrouped: function(){
dataGroups = [];
currentGroup = null;
items.find({}, {sort: {rank: 1}}).forEach(function(item){
if (currentGroup && (!item.tag || currentGroup.tag != item.tag)){
dataGroups.push(currentGroup);
currentGroup = null;
}
if (item.tag){
if (!currentGroup){
currentGroup = {
group: true,
tag: item.tag,
items: [item]
};
} else {
currentGroup.items.push(item);
}
} else {
dataGroups.push(item);
}
});
if (currentGroup){ dataGroups.push(currentGroup); }
return dataGroups;
}
});
Template:
<template name="example">
<div class='panel panel-default'>
<ul class='list-group'>
{{#each itemsGrouped}}
{{#if group}}
<li class="list-group-item">
<a data-toggle="collapse" data-target="#{{tag}}"> Items tagged {{tag}}</a>
</li>
<div id="{{tag}}" class="collapse">
{{#each items}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
</div>
{{else}}
<li class='list-group-item'>{{name}}</li>
{{/if}}
{{/each}}
</ul>
</div>
</template>
If you log the value you are returning from the eachItem helper, you will see you are not closing the last div element. Fixing that may get it working again. However, you have div elements as direct children of the ul element, which you are not supposed to according to the HTML5 specification:
Permitted contents: Zero or more li elements
Also, I think it would be a better option for you to prepare your context in a format that makes it easier to let simple templates take care of the rendering. For example, using the same data from your jsfiddle, suppose you had it in the following format:
tagGroups = [{
names: ["item1", "item2"]
},{
tag: "red",
names: ["item3", "item4"]
},{
tag: "blue",
names: ["item5", "item6", "item7"]
}]
The following templates would give you the expected result in a valid html format. The ending result is a bootstrap panel containing collapsible list-groups:
<template name="accordion">
<div class="panel panel-default">
{{#each tagGroups}}
{{> tagGroup}}
{{/each}}
</div>
</template>
<template name="tagGroup">
{{#if tag}}
{{> namesListCollapse}}
{{else}}
{{> namesList}}
{{/if}}
</template>
<template name="namesList">
<ul class="list-group">
{{#each names}}
<li class="list-group-item">{{this}}</li>
{{/each}}
</ul>
</template>
<template name="namesListCollapse">
<div class="panel-heading"><a data-toggle="collapse" data-target="#{{tag}}">Items tagged {{tag}}</a></div>
<ul id="{{tag}}" class="panel-collapse collapse list-group">
{{#each names}}
<li class="list-group-item">
{{this}}
</li>
{{/each}}
</ul>
</template>
And here is an example of a helper to transform your data so you can make a quick test to see it working:
Template.accordion.tagGroups = function () {
var data = [{
name : 'item1'
},{
name : 'item2'
},{
name : 'item3',
tag : 'red'
},{
name : 'item4',
tag : 'red'
},{
name : 'item5',
tag : 'blue'
},{
name : 'item6',
tag : 'blue'
},{
name : 'item7',
tag : 'blue'
}];
data = _.groupBy(data, 'tag');
data = _.map(data, function (value, key) {
var obj = {};
if (key !== 'undefined') {
obj.tag = key;
}
var names = _.map(value, function (item) {
return item.name;
});
obj.names = names;
return obj;
});
//console.dir(data);
return data;
};
And yes, if the items come from a collection, you will get reactivity by default.
My app is based on the HotTowel template so it includes Durandal, Knockout & Breeze. I have a page with 3 tables side by side. The first table has a list of "templates', the 2nd table shows "sections" for the selected "template" and the 3rd table shows "items" for the selected "section". The "sections" and "items" tables are collections accessed via navigation properties. I find that I get intermittent binding issues. The data in the "templates" table always shows correctly, however related "sections" and "items" sometimes show correctly and other times one of the other is not populated. It would seem to be a timing issue. My view model and view are below. Am I just going about all of this the wrong way?
define(['services/dataservice', 'services/logger', 'services/model'],
function (ds, logger, model) {
var templates = ko.observableArray();
var selectedTemplate = ko.observable();
var selectedSection = ko.observable();
var selectedItem = ko.observable();
var newTemplateTitle = ko.observable();
var newSectionTitle = ko.observable();
var newItemTitle = ko.observable();
function activate() {
newTemplateTitle('');
newSectionTitle('');
newItemTitle('');
logger.log('Templates view activated', null, 'templates', false);
return ds.getTemplatePartials(templates, false, false);//.then(succeeded);
//function succeeded() {
// var firstTemplate = templates()[0];
// setSelectedTemplate(firstTemplate);
//}
}
templates.subscribe(function() {
var firstTemplate = templates()[0];
setSelectedTemplate(firstTemplate);
});
var deactivate = function () {
templates([]);
};
function refresh() {
return ds.getTemplatePartials(templates, true, false);
}
var viewAttached = function (view) {
bindEventToList(view, '#template-list', setSelectedTemplate);
bindEventToList(view, '#section-list', setSelectedSection);
bindEventToList(view, '#item-list', setSelectedItem);
return true;
};
var addTemplate = function () {
var newTemplate = ds.createEntity(model.entityNames.document);
newTemplate.title(newTemplateTitle());
newTemplate.isTemplate(true);
newTemplate.organisation(ds.getCurrentOrganisation()());
return ds.saveChanges().then(saveSucceeded);
function saveSucceeded() {
templates.push(newTemplate);
templates.sort();
newTemplateTitle('');
}
};
var addSection = function () {
var newSection = ds.createEntity(model.entityNames.section);
newSection.title(newSectionTitle());
newSection.isTemplate(true);
newSection.document(selectedTemplate());
return ds.saveChanges().then(saveSucceeded);
function saveSucceeded() {
newSectionTitle('');
}
};
var addItem = function () {
var newItem = ds.createEntity(model.entityNames.item);
newItem.title(newItemTitle());
newItem.isTemplate(true);
newItem.section(selectedSection());
return ds.saveChanges().then(saveSucceeded);
function saveSucceeded() {
newItemTitle('');
}
};
var isTemplateSelected = function (template) {
if (template && selectedTemplate()) {
var thisId = ko.utils.unwrapObservable(selectedTemplate().id);
return ko.utils.unwrapObservable(template.id) == thisId;
}
return false;
};
var isSectionSelected = function (section) {
if (section && selectedSection()) {
var thisId = ko.utils.unwrapObservable(selectedSection().id);
return ko.utils.unwrapObservable(section.id) == thisId;
}
return false;
};
var isItemSelected = function(item) {
if (item && selectedItem()) {
var thisId = ko.utils.unwrapObservable(selectedItem().id);
return ko.utils.unwrapObservable(item.id) == thisId;
}
return false;
};
var vm = {
activate: activate,
deactivate: deactivate,
templates: templates,
//sections: sections,
//items: items,
selectedTemplate: selectedTemplate,
selectedSection: selectedSection,
selectedItem: selectedItem,
title: 'Template Maintenance',
refresh: refresh,
viewAttached: viewAttached,
addTemplate: addTemplate,
addSection: addSection,
addItem: addItem,
newTemplateTitle: newTemplateTitle,
newSectionTitle: newSectionTitle,
newItemTitle: newItemTitle,
isTemplateSelected: isTemplateSelected,
isSectionSelected: isSectionSelected,
isItemSelected: isItemSelected
};
return vm;
//#region internal methods
function setSelectedTemplate(data) {
if (data) {
selectedTemplate(data);
return selectedTemplate().entityAspect.loadNavigationProperty("sections").then(setFirstSectionSelected);
} else {
return false;
}
function setFirstSectionSelected() {
setSelectedSection(selectedTemplate().sections()[0]);
}
}
function setSelectedSection(data) {
if (data) {
selectedSection(data);
return selectedSection().entityAspect.loadNavigationProperty("items").then(setFirstItemSelected);
} else {
selectedSection();
selectedItem();
return false;
}
function setFirstItemSelected() {
setSelectedItem(selectedSection().items()[0]);
}
}
function setSelectedItem(data) {
if (data) {
selectedItem(data);
} else {
selectedItem();
}
}
function bindEventToList(rootSelector, selector, callback, eventName) {
var eName = eventName || 'click';
$(rootSelector).on(eName, selector, function () {
var item = ko.dataFor(this);
callback(item);
return false;
});
}
//#region
}
);
<section>
<div class="row-fluid">
<header class="span12">
<button class="btn btn-info pull-right push-down10" data-bind="click: refresh">
<i class="icon-refresh"></i> Refresh</button>
<h4 class="page-header" data-bind="text: title"></h4>
</header>
</div>
<div class="row-fluid">
<section class="span3">
<header class="input-append">
<input id="newTemplateName"
type="text"
data-bind="realTimeValue: newTemplateTitle"
placeholder="New template name"
class="input-medium" />
<button class="btn btn-info add-on" data-bind="click: addTemplate, disable: newTemplateTitle() === ''">
<i class="icon-plus"></i> Add</button>
</header>
<article>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Templates</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: templates -->
<tr id="template-list" data-bind="css: { 'selected': $root.isTemplateSelected($data) }">
<td data-bind="text: title" />
</tr>
<!-- /ko -->
</tbody>
</table>
<span>Count: <span data-bind="text: templates().length"></span></span>
</article>
</section>
<section class="span5">
<header class="input-append">
<input id="newSectionName"
type="text"
data-bind="realTimeValue: newSectionTitle"
placeholder="New section name"
class="input-medium" />
<button class="btn btn-info add-on" data-bind="click: addSection, disable: newSectionTitle() === ''">
<i class="icon-plus"></i> Add</button>
</header>
<article data-bind="if: selectedTemplate">
<table class="table table-striped table-bordered table-hover" >
<thead>
<tr>
<th data-bind="text: 'Sections for ' + selectedTemplate().title()"></th>
</tr>
</thead>
<tbody>
<!-- ko foreach: selectedTemplate().sections() -->
<tr id="section-list" data-bind="css: { 'selected': $root.isSectionSelected($data) }">
<td data-bind="text: title" />
</tr>
<!-- /ko -->
</tbody>
</table>
<span>Count: <span data-bind="text: selectedTemplate().sections().length"></span></span>
</article>
</section>
<section class="span4">
<header class="input-append">
<input id="newItemName"
type="text"
data-bind="realTimeValue: newItemTitle"
placeholder="New item name"
class="input-medium" />
<button class="btn btn-info add-on" data-bind="click: addItem, disable: newItemTitle() === ''">
<i class="icon-plus"></i> Add</button>
</header>
<article data-bind="if: selectedSection">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th data-bind="text: 'Items for ' + selectedSection().title()"></th>
</tr>
</thead>
<tbody>
<!-- ko foreach: selectedSection().items() -->
<tr id="item-list" data-bind="css: { 'selected': $root.isItemSelected($data) }">
<td data-bind="text: title" />
</tr>
<!-- /ko -->
</tbody>
</table>
<span>Count: <span data-bind="text: selectedSection().items().length"></span></span>
</article>
</section>
</div>
Similar issue (same server, same client but sometimes some navigators not working in breeze) happened to me. In my opinion it can be a timing bug. Or the create/load order of entities count.
I've changed this paralell async load entities from server:
return promise = Q.all([
getPackage(),
getClubPartials(null, true),
getAddressPartials(null, true),
getEventPartials(null, true)
]).then(success);
to this get them one by one:
return getClubPartials(null, true).then(function () {
getAddressPartials(null, true).then(function () {
getEventPartials(null, true).then(function () {
return getPackage().then(success);
})
})
});
And my problem gone!
There is just a lot of code to wade through and try to make sense of in the imagination.
You seem to say it works some of the time. That does sound like a timing issue.
One thing that is disturbing is the async methods (e.g. setSelectedTemplate) that return either false or a promise; not sure why the inconsistency. But that is probably not the real problem.
You could try putting a setTimeout(..., 10) before exiting the async methods. See if that changes the behavior.
If that doesn't reveal it, you'll have to boil it down to just those the essentials that reveal the problem.
Sadly no sudden flash of insight today.
Update June 3
My first concern is that some of your methods return promises and some return the value false. I'd feel more comfortable if they all returned promises. Look at Q.resolve().
Other code baffles me. For example, what is going on in the many variations on this pattern:
function setSelectedItem(data) {
if (data) {
selectedItem(data);
} else {
selectedItem();
}
}
The else{...} isn't doing anything useful. It unwraps the selectedItem property ... and discards the value. What is the point?
And what is the difference between this:
thisId = ko.utils.unwrapObservable(selectedTemplate().id); // obscure
and this?
thisId = selectedTemplate().id(); // clear
Are you unsure whether selectedTemplate().id is a KO observable? The only reason to use unwrapObservable is when you are unsure.
As for timing, you can let KO know that it should re-fresh a binding by calling valueHasMutated() on an observable. For example:
function setFirstItemSelected() {
setSelectedItem(selectedSection().items()[0]);
selectedItem.valueHasMutated();
}
It might help. I don't know ... you're stacking a long chain of dependencies and it isn't easy to see which are bailing out when. That would take study of actual data flows as well as the code. You cannot reasonably expect your StackOverflow audience to work that hard for free.
Try cutting this way down to a super-simple example that displays the troublesome behavior.
Best of luck.