The view code:
<ul data-bind="foreach: BackColorOptions">
<li data-bind="css: { selected: Selected }">
<label>
<input type="radio" name="BackColorOption"
data-bind="value: Color, checked: $root.BackColor" />
</label>
</li>
</ul>
#{
var jsonModel = new System.Web.Script.Serialization.
JavaScriptSerializer().Serialize(Model);
}
<input type="hidden" id="JsonModel" value='#jsonModel' />
The viewmodel code:
var initialData = $.parseJSON($('#JsonModel').val());
function BackColorOption(data, parent) {
var self = this;
self.parent = parent;
self.Text = ko.observable(data.Text);
self.Color = ko.computed(function () {
return '#' + self.Text().toLowerCase();
});
self.Selected = ko.computed(function () {
var backColor = self.parent.BackColor();
if (backColor) {
return backColor.toLowerCase() == self.Color;
}
return false;
});
}
function TestViewModel() {
var self = this;
self.BackColor = ko.observable(initialData.BackColor);
var mappedBackColorOptions = $.map(initialData.BackColorOptions,
function (item) {
return new BackColorOption(item, self);
}
);
self.BackColorOptions = ko.observableArray(mappedBackColorOptions);
}
ko.applyBindings(new TestViewModel());
The model code:
string BackColor { get; set; }
SelectListItem[] BackColorOptions
{
get
{
return new[]
{
new SelectListItem{Text = "cc0000"},
new SelectListItem{Text = "ff9900"},
new SelectListItem{Text = "dddd33"},
new SelectListItem{Text = "009900"},
new SelectListItem{Text = "00cccc"},
new SelectListItem{Text = "0066ff"},
new SelectListItem{Text = "9900ff"},
new SelectListItem{Text = "ff00ff"},
};
}
}
The code above works as expected in IE (8) and Chrome (17), but not FF (10.0.2). I'm basically trying to do a color selector similar to GitHub's issue labels. The view renders a set of radio buttons you can click on to choose a color. When a radio is checked, I add a selected css class to the parent <li>. The css class causes a checkmark icon to appear over the <li>.
In Firefox, the selected css class is only applied after a user has gone through and checked each radio button at least once. I debugged and found that this is because of the way the self.Color computed observable is evaluated in the BackColorOption closure. Before the first time a radio is checked, typeof(self.Color) == 'function' evaluates to true. However after it is checked, typeof(self.Color) == 'string' evaluates to true.
This typeof(self.Color) behavior is the same according to both Firebug and Chrome's js debugger. However the issue in FF is because of this line in the self.Selected computed observable in the BackColorOption closure:
return backColor.toLowerCase() == self.Color;
Chrome & IE still return true even when self.Color is a function instead of a string. However Firefox does not. When self.Color is a function, it returns false. This is why you have to check each radio at least once before the css class is added to the <li> and the icon appears.
I'm still a bit new to knockout, and may not be appropriately calling a viewmodel property as a function when supposed to. I'm still a little unclear when to use the () parenthesis and when to omit them. Is there another way I should write the self.Selected computed observable, which depends on the self.Color computed observable (in the BackColorOption closure)?
Update 1
I was able to get this to work in FF 10.0.2 with the following:
self.Selected = ko.computed(function () {
var backColor = self.parent.BackColor();
var selfColor = self.Color;
if (typeof (selfColor) === 'function')
selfColor = self.Color();
if (backColor) {
return backColor.toLowerCase() === selfColor;
}
return false;
});
However, this feels like I'm fighting knockout. Isn't it supposed to "just work"?
The value binding in KO is not really ideal for radio buttons and checkboxes. In your situation you need your radio buttons to have a value attribute so that when you click them, that value can be used to update your TestViewModel.BackColor observable.
Normally with radio buttons, you don't want the value attribute to change (if ever) once the html is rendered.
So, I've changed your html template from using a value binding to instead using an attr binding (html attribute). This is now just setting the value html attribute of your radio buttons. The checked binding then keeps your TestViewModel.BackColor observable synced with whatever the value is of the radio button that is checked.
See this fiddle:
http://jsfiddle.net/m2KQ2/
Also, the line in your BackColorOption function has a typo:
self.Selected = ko.computed(function () {
var backColor = self.parent.BackColor();
if (backColor) {
return backColor.toLowerCase() == self.Color; //<-- should be Color();
}
return false;
});
Related
I am using a select2 using MVC 5 and C#.
I'm having trouble with the dropdownlist (select2) loading with initial data of the model.
The items passed in the corresponding binding field properly valued, but they are not shown in select2!
I mean, despite the list of the ViewModel field correctly valued by the controller, the dropdownlist (select2) is not valued correctly, as if the binding model did not work.
Needless to say, I'm googling for 1,5 days.
Fortunately (:)) I have no problem at the loading of select2 with all items, the dropdownlist works correctly even on the post, even I can take the selected items.
Many Thanks to all
P.s: Now that I'm writing, I have a doubt; Could be that select2 doesn't work with List ?
View
#section scripts{
...
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
}
#Html.DropDownListFor(model => model.MezziStraSelect, Model.MezziStraOrdinari, new { style = "width: 100%", #class = "form-control" })
JS
$(document).ready(function () {
//...
$("#MezziStraSelect").select2({
placeholder: "Select one or more items",
multiple: true,
allowClear: true
});
#if ( Model.MezziStraSelect == null)
{
<text>$("#MezziStraSelect").val(null).trigger("change");</text>
}
}
ViewModel
public Guid[] MezziStraSelect { get; set; }
public MultiSelectList MezziStraOrdinari { get; set; }
Controller
//Load List MezziStraOrdinari
var _stra = m.GetMezziStraordinari().Select(x => new
{
id = x.VoceSpesaID,
desc = x.VoceSpesa
}).ToList();
//view model set field
vm.MezziStraOrdinari = new MultiSelectList(_stra, "id", "desc");
//Load array Mezzi used from item selected
List<Guid> _mezziStraUsati = new List<Guid>();
var elems = dc.ItemSelected.FirstOrDefault(x => x.ItemID.ToString() == _guidSelected);
if (elems!=null)
{
elems.VociSpese.ToList().ForEach(x =>
{
if (x.VociSpesa.Straordinario == true)
_mezziStraUsati.Add(x.VoceSpesaViaggioID); //VoceSpesaViaggioID is GUID
});
if (_mezziStraUsati.Count>0)
vm.MezziStraSelect = _mezziStraUsati.ToArray(); //Guid[]
}
The initial loading of the Select2 unfortunately the binding cannot set the initial values!
So reading the Select2 documentation to this link , I had to proceed with a manual upload via JS.
Practically to try to do an human thing (I hope it is), I created an Extension method in the backend of the Model field
Extension Method
public static string ToSelect2Array<T>(this T[] values)
{
var resp = string.Empty;
values.ToList().ForEach(x => resp += $"'{x.ToString()}',");
if (resp.Length > 0)
resp = resp.Substring(0, resp.Length - 1);
return resp;
}
and then in JS client side, I call it like this:
VIEW (script JS)
#if ( Model.MezziStraSelect != null)
{
<text>
$("#MezziStraSelect").val([#Html.Raw(Model.MezziStraSelect.ToSelect2Array())]);
$("#MezziStraSelect").trigger('change');
</text>
}
else
{
<text>$("#MezziStraSelect").val(null).trigger("change");</text>
}
This is working quite well, but is it possible that there is no way to get it to bind automatically?
I would be curious to know other solutions, more elegant than this I would be grateful for!
I am trying to understand why this isn't working. If i set the value of myTextBox to 5 then trigger the change event. the value for my myTextBox is null. If i change the value to 10 then fire the event again, the value will be the previous one (5). I've compared it to one of the many textboxes working on the form and they appear the same. The only difference is the value property in the wrapper object is set in the ones that work but behind in the one that doesn't. Digging into the both objects element property, i see the values are correct and current. Any help would be appreciated.
Model Property
[UIHint("Decimal")]
[Display(Name = "Example")]
public decimal? MyTxt{ get; set; }
Template (Decimal.cshtml):
#model decimal?
#{
var defaultHtmlAttributesObject = new { style = "width:100%" };
var htmlAttributesObject = ViewData["htmlAttributes"] ?? new { };
var htmlAttributes = Html.MergeHtmlAttributes(htmlAttributesObject, defaultHtmlAttributesObject);
}
#(Html.Kendo().NumericTextBoxFor(m => m)
.Format("#.00")
.HtmlAttributes(htmlAttributes)
)
UI Declaration
#Html.EditorFor(m => m.MyTxt, new { htmlAttributes = new { #style="width: 100%", #readonly = "readonly" } })
Javascript:
var myTextBox = $('#MyTxt').data('kendoNumericTextBox');
$(document).on('change', '#foo', function(){
var test = myTextBox.value();
})
Update:
In the document ready function i was binding the change event like this:
$('#MyTxt').change({ source: $('#MyTxt'), destination: someObject, isTwoMan: true, crewType: LaborType.Set }, SomeCalcFunction);
The jQuery change event fires nefore the kendo one thus the delay in getting the correct value. The fix was to read the manual and bind to the event the kendo way
the issue was in the way i was binding to the change event.
$('#MyTxt').change({ source: $('#MyTxt'), destination: someObject, isTwoMan: true, crewType: LaborType.Set }, SomeCalcFunction);
will fire before the kendo event does. Changing it the kendo way fixed my issue
$("#MyTxt").bind("change", function (e) {
var event = jQuery.Event('change', {
source: $("#MyTxt"),
destination: someObject,
isTwoMan: true,
crewType: LaborType.Set
});
SomeCalcFunction(event);
});
hello everyone I want to ask a question abuout mvc dropdownlist. I am trying to filter the datas to their places or their codes while dropdowns selected index changed. I can do it when I use one dropdown but when I use more than one dropdown I cannot get the results separately.
As you see in the picture I have two dropdownlist.
public ActionResult Index(int? id,int? ddwList)
{
Repository<Order> _ro = new Repository<Order>();
IEnumerable<SelectListItem> _orderSelectListItem = _ro.All().AsEnumerable().Select(s => new SelectListItem
{
Text = s.code,
Value = s.id.ToString()
});
ViewData["ddOrder"] = _orderSelectListItem;
Repository<Workshop> _rw = new Repository<Workshop>();
IEnumerable<SelectListItem> _workshopSelectListItem = _rw.All().AsEnumerable().Select(s => new SelectListItem
{
Text = s.name,
Value = s.id.ToString()
});
ViewData["ddwList"] = _workshopSelectListItem;
Repository<ClothShipment> _rcs = new Repository<ClothShipment>();
IEnumerable<MyClothShipment> _myClothShipment = null;
if (id != null)
{
int? idOrd = _rcs.Find(w => w.orderId == id).orderId;
//int? idWork = _rcs.Find(w => w.workshopId == id).workshopId;
if (idOrd != null)
{
_myClothShipment = _rcs.All().Where(w => w.orderId == id).Select(s => new MyClothShipment
{
id = s.id,
amount = s.amount,
orderName = s.order.code,
clothName = s.clothList.name,
workshopName = s.workshop.name,
shipDate = s.shipDate
});
}
//else if(idWork != null){
// _myClothShipment = _rcs.All().Where(w => w.workshopId == id).Select(s => new MyClothShipment
// {
// id = s.id,
// amount = s.amount,
// orderName = s.order.code,
// clothName = s.clothList.name,
// workshopName = s.workshop.name,
// shipDate = s.shipDate
// });
//}
}
else {
_myClothShipment = _rcs.All().Select(s => new MyClothShipment
{
id = s.id,
amount = s.amount,
orderName = s.order.code,
clothName = s.clothList.name,
workshopName = s.workshop.name,
shipDate = s.shipDate
});
}
return View(_myClothShipment);
}
my view is here
<div id="sample_editable_2_length" class="dataTables_length">
<label>
#Html.DropDownList("ddwList",(IEnumerable<SelectListItem>)ViewData["ddwList"],"Atölye Seçiniz",new {#id="StateDropDown1", #class = "span15 chosen"})
</label>
</div>
my view is here
<div id="sample_editable_2_length" class="dataTables_length">
<label>
#Html.DropDownList("ddwList",(IEnumerable<SelectListItem>)ViewData["ddwList"],"Atölye Seçiniz",new {#id="StateDropDown1", #class = "span15 chosen"})
</label>
</div>
<div id="sample_editable_1_length" class="dataTables_length">
<label>
#*<select class="m-wrap small" name="sample_editable_1_length" size="1" aria-controls="sample_editable_1">
</select>*#
#Html.DropDownList("ddOrder",(IEnumerable<SelectListItem>)ViewData["ddOrder"],"Sipariş Kodu Seçiniz",new {#id="StateDropDown", #class = "span15 chosen"})
</label>
</div>
and here is my script code
$("#StateDropDown").change(function (e) {
var controllerName = '#ViewContext.RouteData.Values["Controller"].ToString()';
var actionName = '#ViewContext.RouteData.Values["Action"].ToString()';
var _id = $("#StateDropDown").val();
var _url = "/" + controllerName + "/" + actionName + "/" + _id;
window.location.href =_url
});
$("#StateDropDown1").change(function (e) {
var controllerName = '#ViewContext.RouteData.Values["Controller"].ToString()';
var actionName = '#ViewContext.RouteData.Values["Action"].ToString()';
var _id = $("#StateDropDown1").val();
var _url = "/" + controllerName + "/" + actionName + "/" + _id;
window.location.href = _url
});
I am filling the dropdowns when on page load from database and getting all the data to show with dropdowns I want to filter the data that shown... And with this code one of my dropdown works I am taking the id of selected item (Index(int? id)) in here but when I try to use both of them separately it doesnt work how can I make both of them work. What should I do ? The second parameter always comes null or if I use different parameter except "id" it is again coming null ? and also I tried to take parameter as string but it also came null... Thank you for your helps.
To explain what your code is doing:
When your select a value from your first select, you are passing its value to the Index method (e.g. /Index/1) so the value of parameter id is 1 but no value has been passed to parameter ddwList so it is null. When you select a value from the second select you are passing its value to the index method (e.d. /Index/5) so the value of parameter id is 5 but no value has been passed to parameter ddwList so again it is null.
Assuming you want to display a table based on the selections of both selects, then you need to construct the url as /Index?id=1&ddwList=5. Therefore, remove the change events from your selects and add a button that constructs the query in its click event. However, the way you are doing this is reloading the whole page each time. I suggest you consider loading the table from a partial view using a jQuery.get() method to avoid a complete page load each time. For example
public ActionResult Index()
{
// Build the 2 select lists only
return View();
}
Index view
// Add the two #Html.DropdownFor()...
<button id="LoadTable">Load Table</button>
<div id="TablePlaceholder"></div>
and the script
$('#LoadTable').click(function() {
var id1 = // the value of the first select
var id2 = // the value of your second select
$.get('#Url.Action("Table")', { id: id1, ddwList: id2 }, function(data) {
$('#TablePlaceHolder').html(data);
});
}
and the partial result
public ActionResult Table(int? id, int? ddwList)
{
// Build your table view based on values of id and ddwList
return PartialView();
}
If I understand this, you are able to use one of the drop downs at a time and that drop down successfully sends it's value to your controller while the other drop down always sends null. If that's the case, that is working correctly with the way you made your dropdownlists with an option label.
As explained in the msdn documentation:http://msdn.microsoft.com/en-us/library/dd492256(v=vs.118).aspx
optionLabel Type: System.String The text for a default empty item.
This parameter can be null.
So if you want both of the drop down lists to be usable at the same time, you'll need to remove the events for .change and add a form with those dropdowns AND a submit button within to use both values at the same time.
OR
Do not use an option label meaning the first option of the dropDownList will be used as it's initial/default value. Here is a link to the msdn docs showing the different ways to format the Html.DropDownList helper: http://msdn.microsoft.com/en-us/library/system.web.mvc.html.selectextensions.dropdownlist(v=vs.118).aspx
I hope I understood you correctly!
I have the following scenario:
Multiple requiredif DataAnnotation attributes. I made a custom label helper that renders a "*" aside for the properties that are decorated if requiredif attribute.
Now on the clientside I want to be able to hide/show if the dependent property changes value.
For a more precise example I have a model
public class Document {
public bool IsFake {get; set; }
[RequiredIf("IsFake",false,ValueComparison.IsEqual)]
public string Number{ get; set; }
}
Based on the label helper that I made I have the corresponding label for Number with a red * in the UI. When I change on the client side from the is fake to the is not fake radio button I want to hide the *.
I want to do be able to make this changes automatic and not make a script that for the known fields does that, as I have multiple cases like this.
I was thinking maybe I could write a javascript code that attaches dynamically a change event to the dependent property input and a handler that would show/hide the required mark.
This is pretty much the solution which I came up with.
It is the best I could do, but still needs some customization meaning that the span that contains the "*" it's custom for my pages' DOM.
<div class="editor-label">
<label ui-documentLabel" for="Number">Number<span class="span-required">*</span></label>
</div>
<div class="editor-field">
<input class="k-textbox ui-textbox" data-val="true" data-val-requiredif="The field Number is required!" data-val-requiredif-dependentproperty="IsFake" data-val-requiredif-dependentvalue="False" data-val-requiredif-operator="IsEqual" id="Number" name="Number" value="" type="text">
<span class="field-validation-valid" data-valmsg-for="Number" data-valmsg-replace="true"></span>
</div>
and the javascript
var clietsidevalidation = function () { };
clietsidevalidation.is = function (value1, operator, value2) {
//function that verifies that the comparison between value1 and value2 is true or not
};
clietsidevalidation.handleRequirefIf = function (container) {
$('input', container).filter(function () {
var attr = $(this).attr('data-val-requiredif');
return (typeof attr !== 'undefined' && attr !== false);
}).each(function (index, item) {
var params = new Array();
params["operator"] = $(this).attr('data-val-requiredif-operator');
params["dependentvalue"] = $(this).attr('data-val-requiredif-dependentvalue');
params["dependentproperty"] = $(this).attr('data-val-requiredif-dependentproperty');
var dependentProperty = clietsidevalidation.getName(this, params["dependentproperty"]);
var dependentTestValue = params["dependentvalue"];
var operator = params["operator"];
var dependentPropertyElement = document.getElementsByName(dependentProperty);
params["element"] = this;
$(dependentPropertyElement).on('change', { params: params }, function (e) {
var input = e.data.params.element;
var inputName = $(input).attr("name");
var $span = $('label[for=' + inputName + '] span', '.editor-label');
var dependentProperty = this;
var dependentTestValue = e.data.params["dependentvalue"];
var operator = e.data.params["operator"];
var dependentPropertyElement = $(this);
var dependentValue = null;
if (dependentPropertyElement.length > 1) {
for (var index = 0; index != dependentPropertyElement.length; index++)
if (dependentPropertyElement[index]["checked"]) {
dependentValue = dependentPropertyElement[index].value;
break;
}
if (dependentValue == null)
dependentValue = false
}
else
dependentValue = dependentPropertyElement[0].value;
if (clietsidevalidation.is(dependentValue, operator, dependentTestValue) == false) {
$span.addClass('hidden');
var $form = $span.closest("form");
// get validator object
var $validator = $form.validate();
var $errors = $form.find("span.field-validation-error[data-valmsg-for='" + inputName + "']");
// trick unobtrusive to think the elements were succesfully validated
// this removes the validation messages
//custom form our solution as the validation messages are differently configured DOM
$errors.each(function () { $validator.settings.success($('label',this)); })
// clear errors from validation
$validator.resetForm();
}
else {
$span.removeClass('hidden')
}
});
});
};
This is inspired by another post which I can't find right now, but when I do I will post a link.
I am trying to implement a custom validation on my ASP.NET MVC3 form.
The first custom validation is only validating if a file has been selected in the file upload input.
It worked fine when I had only one client validation method. When I tried to add a second one. The second validation method is never triggered.
The GetValidationRules method in my attribute class
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "file",
ErrorMessage = "ResumeEmptyError".Translate("fordia_subform")
};
var rule2 = new ModelClientValidationRule
{
ValidationType = "extension",
ErrorMessage = "ResumeFileFormatError".Translate("fordia_subform")
};
var list = new List<ModelClientValidationRule>();
list.Add(rule2);
list.Add(rule);
return list;
}
My javascript code in my view
<script type="text/javascript">
jQuery.validator.addMethod("file", function (value, element) {
return $('#ResumeFileName').val() != '';
});
jQuery.validator.addMethod("extension", function (value, element) {
return $('#ResumeFileName').val() == 'a';
});
jQuery.validator.unobtrusive.adapters.add("file", function (options) {
options.rules["file"] = options.params.param;
if (options.message) {
options.messages['file'] = options.message;
}
});
jQuery.validator.unobtrusive.adapters.add("extension", function (options) {
options.rules["extension"] = options.params.param;
if (options.message) {
options.messages["extension"] = options.message;
}
});
</script>
When I look at my HTML source, I have the following HTML attributes on my input element :
<input data-val="true" data-val-extension="Erreur: format error" data-val-file="Required" id="Resume" name="Resume" type="file" value="" class="input-validation-error">
What am I missing to have multiple client validation methods on this form?
In the script you have shown you seem to be using some options.params.param parameter which is never declared nor passed from your validation attribute. So at its present form your script won't work even with a single rule. You said it was working but I guess you must have changed something in the code because what you have shown has no chance of working.
So if you don't have parameters here's what you could do (notice the empty array passed as second argument to the add adapter method):
jQuery.validator.addMethod("file", function (value, element) {
return $('#ResumeFileName').val() != '';
});
jQuery.validator.unobtrusive.adapters.add("file", [], function (options) {
options.rules["file"] = options.params;
if (options.message) {
options.messages['file'] = options.message;
}
});
jQuery.validator.addMethod("extension", function (value, element) {
return $('#ResumeFileName').val() == 'a';
});
jQuery.validator.unobtrusive.adapters.add("extension", [], function (options) {
options.rules["extension"] = options.params;
if (options.message) {
options.messages["extension"] = options.message;
}
});
and if you have parameters you will need to declare them first on the validation rules returned by the attribute and then use them in the adapter as shown in this post.