Razor - Converting Json result from controller to a complex object - javascript

i have a partial view "_SearchPanel" that has year list dropdown, a classes multiselect control, (some other drop downs - ommitted) and a search button.
I want that when i change selection in year list drop down, only my classes list is refreshed/updated, and not the whole partial view on page.
So i use a JsonResult action in my controller (as opposed to the first time load)
public JsonResult BindClasses(int yearId)
{
ClassRepository repClass = new ClassRepository("name=ge");
YearRepository repYear = new YearRepository("name=ge");
var dataClass = repClass.GetClassesByYear(yearId);
var groupedClassOptions = dataClass.GroupBy(x => x.grade).Select(x => new OptionGroupVM()
{
GroupName = "Grade " + x.Key.ToString(),
Options = x.Select(y => new OptionVM()
{
Value = y.classID.ToString(),
Text = y.classname
})
});
return Json(groupedClassOptions);
}
My javascript
var dropDownYear = $('#ddlYear');
dropDownYear.change(function(){
$("#classList").load(url, {yearId: $(this).val()}, function(result){
setOptions($('#classList'), #Html.Raw(Json.Encode(new List<int>(){})), result);
});
});
now the problem is this result is not considered as an object as was the first time (onpageload) here:
jQuery(function ($) {
setOptions($('#classList'), #Html.Raw(Json.Encode(Model.SelectedClasses)), #Html.Raw(Json.Encode(Model.ClassOptions)));
}
How do i correct/cast it to be considered as Model.ClassOptions(type: GroupOptionsVM List) object instead of a Json
What I have tried
var url = '#Url.Action("BindClasses", "Maps")';
var dropDownYear = $('#ddlYear');
dropDownYear.change(function(){
$("#classList").load(url, {yearId: $(this).val()}, function(result){
#{var x = new List<OptionGroupVM>();}
x = result;
setOptions($('#classList'), #Html.Raw(Json.Encode(new List<int>(){})), x);
});
});
this gives me some syntax errors!!
UPDATE
[Referring to the previous question Stephen linked in comments]
Since i had to do it for two dropdown lists with slight difference i had created setOptions function in my script
function setOptions(listBox, selected, groups) {
// Generate options
createGroupedOptions(listBox, selected, groups);
// Attach plug-in
listBox.multiselect({ enableClickableOptGroups: true, onChange: function(){
var selectedClassItems = this.$select.val();
} });
}
function createGroupedOptions(element, selected, groups) {
for (var i = 0; i < groups.length; i++) {
var group = groups[i];
var groupElement = $('<optgroup></optgroup>').attr('label', group.GroupName);
for (var j = 0; j < group.Options.length; j++) {
var option = group.Options[j];
var optionElement = $('<option></option>').val(option.Value).text(option.Text);
if (selected) {
if (selected.toString().indexOf(option.Value) >= 0) {
optionElement.attr('selected', 'selected')
}
} else {
if (option.IsSelected) {
optionElement.attr('selected', 'selected')
}
}
$(groupElement).append(optionElement);
}
$(element).append(groupElement);
}
}
CALLING setOptions function
setOptions($('#classList'), #Html.Raw(Json.Encode(Model.SelectedClasses)), #Html.Raw(Json.Encode(Model.ClassOptions)));
setOptions($('#indicatorList'), #Html.Raw(Json.Encode(Model.SelectedIndicators)), #Html.Raw(Json.Encode(Model.IndicatorOptions)));

Your returning json, so using .load() makes no sense (you would typically use that when the method your calling returns a partial view).
Change your script to create the <optgroup> and <option> elements based on your data your method returns
var url = '#Url.Action("BindClasses", "Maps")';
var dropDownYear = $('#ddlYear');
dropDownYear.change(function() {
$.post(url, { yearId: $(this).val() }, function(data) {
$.each(data, function(index, item) {
var group = item.GroupName;
// use the above to build your <optgroup> element
$.each(item.Options, function(index, item) {
var value = item.Value;
var text = item.Text;
// use the above to build your <option> elements and append to the <optgroup> element
});
// append the <optgroup> to the <select id="classList"> element
});
});
});
Note the details of the code for generating the elements are in the answer to your previous question

You are trying to mix client side code (jQuery) with server side code (.NET) and it won't work. #Html.Raw and JsonEncode are server side methods. You can't use them after the page loads.
In essence, you need to either use jQuery for all of your page interaction and manage the state of the page on the client side or use full MVC (postback) and do everything on the server.
There are technically other options but I just wanted to address the fundamental issue with what you have tried so far.

Related

How to dynamically check the fields and create array?

In my angular app, i am building a pivot table using pivot.js, which works prett well.
I need to load all the indexes from elastic search, when the user changes the index, it has to load the corresponding data to the pivot table. This i have achieved with the following code.
Now my issue is whenever user changes the index, i make a service call to get the fields of the table and the complete data of the table. I need to dynamically build an array for the pivot table.
Here is a sample, i have a product table with the fields and data corresponding.
I need to dynamically map the fields and obtain the data to be bind.
//assume these are the fields of a table i get from the service
$scope.ProductFields = ['Name','Code','UOM'];
//assume this is the data i get from the rest service
$scope.ProductData = [
{
Name: "ProductA",
Code: "#f00",
UOM:"Units"
},
{
Name: "ProductB",
Code: "#0f0",
UOM:"Units"
}
// how can i build a dynamic object of product from the above data with the fields
//something like this
//for(dataItem in ProductData)
// {
// for(x in ProductFields)
// {
// $scope.datatobeBuild.push({fields[x]:data[dataItem]});
// }
// }
// $scope.products=$scope.datatobeBuild;
];
Here is the complete Code
You can build the objects using nest loops:
$scope.products = [];
for (var i = 0; i < $scope.ProductData.length; i++) {
var data = $scope.ProductData[i],
product = {};
for (var j = 0; j < $scope.ProductFields.length; j++) {
var field = $scope.ProductFields[j];
product[field] = data[field];
}
$scope.products.push(product);
}
or
$scope.products = [];
$scope.ProductData.forEach(function(data) {
var product = {};
$scope.ProductFields.forEach(function(field) {
product[field] = data[field];
});
$scope.product.push(product);
});
You need to construct object with dynamic keys. You can don it like this using bracket notation:
$scope.ProductData.forEach(function(data) {
$scope.ProductFields.forEach(function(field) {
var dataItem = {};
dataItem[field] = data;
$scope.datatobeBuild.push(dataItem);
});
});

CRM Javascript Automatically Populated a Look-up Value with a specific field

I'm trying to write a javascript on CRM Phone Call page. We have a custom look-up field called new_department, and we want to automatically populate the field with value "IT" (there should be one) when the form is opened.
The thing is we have a separate Dev and Production CRM link therefore I cannot just assign a hard-coded GUID value into this field. So first I wrote a Rest Retrieve Multiple to get the correct department.
Then my problem is I'm not sure about the result returned from this Retrieve Multiple. How do I grab just the GUID from Rest? I'm seeing that this is a type of {Object}. Then lastly how do I go about setting the lookup value after retrieving the {Object}? Any help is greatly appreciated.
Here is my code.
function phonecall() {
var formType = Xrm.Page.ui.getFormType();
if (formType == 1) //create
{
//RetrieveMultiple function
var DepartmentId = getITDepartment();
//set the lookup value
var ID = DepartmentId.id;
var departmentValue = new Array();
departmentValue[0] = new Object();
departmentValue[0].id = DepartmentId;
departmentValue[0].name = 'IT';
userValue[0].entityType = "new_department";
Xrm.Page.getAttribute("new_department").setValue(departmentValue);
}
}
function getITDepartment()
{
XrmServiceToolkit.Rest.RetrieveMultiple("new_departmentSet", "$select=new_departmentId&$filter=new_name eq 'IT'",
function (results) {
if (results.length > 0)
resultList = results;
}, function (error) { alert(error); }, function onComplete() { }, false);
return resultList;
}
Thanks much.
I'm not familiar with XrmServiceToolkit but here how code could look like to work properly - I replaced only assigning part:
var DepartmentId = getITDepartment();
if (DepartmentId != null && DepartmentId.length > 0){
Xrm.Page.getAttribute("new_department").setValue([{
id: DepartmentId[0].new_departmentId,
name: "IT",
entityType: "new_department"
}]);
}
You are setting the lookup value correctly, you just need to get the Id correctly. The results variable is an array of new_department records, so try something like this:
var resultId = null;
XrmServiceToolkit.Rest.RetrieveMultiple("new_departmentSet", "$select=new_departmentId&$filter=new_name eq 'IT'",
function (results) {
if (results.length > 0)
resultId = results[0].new_departmentId; //gets the first record's Id
}, function (error) { alert(error); }, function onComplete() { }, false);
return resultId;

Protractor test hangs when clicking on element

I have been trying to write a protractor test that selects an item from a custom dropdown menu. The only problem is that when it tries to click an element other than the last one in the list it hangs and timesout. When I remove the click() method invocation it seems to work fine. Since all these calls are done asynchronously I also don't see a way of stopping the loop when it finds the element. My code looks like this:
var it = null;
for(var i = 1; i <= totalNumberOfAccounts; i++) {
var listItemLocator = '//div[#id="payment-accounts"]/div/ul/li[' + i + ']/label/div/div[2]/div[2]/span[2]';
var item = browser.driver.findElement(protractor.By.xpath(listItemLocator));
item.getText().then(function(value) {
if(value === accountNumber) {
it = item;
}
console.log(value);
})
.then(function clickOption() {
console.log('Clicking...');
if (it) {
console.log('Clicking desired item');
it.click();
console.log('Clicked..');
}
})
}
I also tried this approach:
this.selectRichSelectOption = function (selector, item) {
var selectList = browser.driver.findElement(selector);
selectList.click();
var desiredOption = '';
var i = 1;
selectList.findElements(protractor.By.tagName('li'))
.then(function findMatchingOption(options) {
console.log(options);
options.some(function (option) {
console.log('Option:');
console.log(option);
var listItemLocator = '//div[#id="payment-accounts"]/div/ul/li[' + i + ']/label/div/div[2]/div[2]/span[2]';
console.log(listItemLocator);
var element = option.findElement(protractor.By.xpath('//label/div/div[2]/div[2]/span[2]'));
console.log('Element:');
console.log(element);
i++;
element.getText().then(function (value) {
console.log('Value: ' + value);
console.log('Item:');
console.log(item);
if (item === value) {
console.log('Found option..');
desiredOption = option;
return true;
}
return false;
});
});
})
.then(function clickOption() {
console.log('Click option');
console.log(desiredOption);
if (desiredOption) {
console.log('About to click..');
desiredOption.click();
}
});
};
The result of this one is even more strange. Now all of a sudden the getText() method invocation returns an empty String. But when I try to retrieve the e.g. the class attribute I get the correct value back. Where did the Text value go?
Can somebody please help me out?
This seems to be an issue with page load. After you select, the page does not load completely.
Try using a browser.sleep(timeInMs);
try using node 8+'s async functions such as await. I went through this headache and it was solved by awaiting for certain things to appear or have certain attributes.
await browser.wait(EC.presenceOf(element(by.xpath('path leading to element based off attribute'))))
Good luck

Callback in JavaScript loop causes issue

I have a function that consumes data with a WCF service (in SharePoint). The service does not return a specific field that I need for items so I use the SharePoint Client Object Model to query for the field by using the ID I have in the returned result from the WCF service.
function LoadAllNews() {
var listUrl = "/_vti_bin/ListData.svc/Pages";
$.getJSON(listUrl,
function (data) {
$.each(data.d,
function (i, result) {
GetImageUrl(result.Id, function (image) {
$(ConstructHtml(image, result.Title, result.Path, result.Name)).appendTo("#News");
});
});
});
}
When I debug result here I always get the items returned in the same order but since the GetImageUrl executes a query async the items are not appended in the same order. Most of the times they do must some times it appears to be random since time to get the image varies:
function GetImageUrl(id, callback) {
var context = new SP.ClientContext();
var items = context.get_web().get_lists().getByTitle('Pages').getItemById(id);
context.load(items);
context.executeQueryAsync(function () {
callback(items.get_item('PublishingRollupImage'));
});
}
function ConstructHtml(imageUrl, title, path, name) {
var html = "" // a long html string..
return html;
}
I could post this on sharepoint.stackexchange but the audience is wider here and it's more of a question how to handle this with JavaScript than with SharePoint itself.
Any ideas on how I should approach this? I was thinking something like skip the image in LoadAllNews() and then when all items are appended use JavaScript/jQuery to load the image for each news item.
Thanks in advance.
Based on the fork function from my answer to this question: Coordinating parallel execution in node.js. I would do it like this:
var getImages = [];
var meta = [];
$.each(data.d,
function (i, result) {
getImages.push(function(callback){
GetImageUrl(result.Id, callback);
});
meta.push({
title : result.Title,
path : result.Path,
name : result.Name
});
});
fork(getImages,function(images) {
$.each(images,function(i,image){
$(ConstructHtml(
image,
meta[i].title,
meta[i].path,
meta[i].name
)).appendTo("#News");
});
});
The implementation of fork is simply this:
function fork (async_calls, shared_callback) {
var counter = async_calls.length;
var all_results = [];
function makeCallback (index) {
return function () {
counter --;
var results = [];
// we use the arguments object here because some callbacks
// in Node pass in multiple arguments as result.
for (var i=0;i<arguments.length;i++) {
results.push(arguments[i]);
}
all_results[index] = results;
if (counter == 0) {
shared_callback(all_results);
}
}
}
for (var i=0;i<async_calls.length;i++) {
async_calls[i](makeCallback(i));
}
}
The fork function above gathers asynchronous results in order so it does exactly what you want.
If the order of events matters, make it a synchronous procedure

How to Get XML file with Jquery and Display Elements in Random Order ONCE per load?

I have been trying to build a question and answer app with Ajax. I need help creating a particular function. I have created XML files with different questions and answers. The basic idea is to use the "get" function to (1) load an XML questions file and (2) use the "display" and "math.random" functions to display a random "question" element (the corresponding "answer" element will be shown at the same time, but hidden from view by Javascript.) This is the format of the XML files I am using. These nodes are enclosed by a parent node, Words..
<WordQuestions>
<Question>Question1</Question>
<Answer>Answer1</Answer>
</WordQuestions>
<WordQuestions>
<Question>Question2</Question>
<Answer>Answer2</Answer>
</WordQuestions>
I need to create a function that can choose a question & answer element at random from the XML file, show it to the user, but NOT show it again on subsequent clicks by the user. So, once a question is shown to the user, it needs to be removed from the list of questions to be shown to the user on the next click. Does anybody know how to do this?
I have created a similar function that works like a charm, but it is limited in that it is too random - a questions & answer element may never be selected to show to the user, or it could be selected a disproportionate number of times. The user needs to practice with all of the questions. Here is a stripped-down version of this function.
<script language = "javascript">
function getCategory()
{
var XMLHttpRequestObject = false;
if (window.XMLHttpRequest) {
XMLHttpRequestObject = new XMLHttpRequest();
XMLHttpRequestObject.overrideMimeType("text/xml");
} else if (window.ActiveXObject) {
XMLHttpRequestObject = new
ActiveXObject("Microsoft.XMLHTTP");
}
if(XMLHttpRequestObject) {
var P = document.LoadCategory.Load.value;
if (P == "Category1") {XMLHttpRequestObject.open("GET", "Catgory1.xml", true)}
if (P == "Category2") {XMLHttpRequestObject.open("GET", "Category2.xml", true)}
if (P == "Category3") {XMLHttpRequestObject.open("GET", "Category3.xml", true)}
XMLHttpRequestObject.onreadystatechange = function()
{
if (XMLHttpRequestObject.readyState == 4 &&
XMLHttpRequestObject.status == 200) {
var xmlDocument = XMLHttpRequestObject.responseXML;
displayCategory(xmlDocument);
}
}
XMLHttpRequestObject.send(null);
}
}
function displayCategory (xmldoc)
{
Questionnodes = xmldoc.getElementsByTagName("Question");
Answernodes = xmldoc.getElementsByTagName("Answer");
var i = Math.floor((Math.random()*1000)%Questionnodes.length);
var i = Math.floor((Math.random()*1000)%Answernodes.length);
var displayText1 =
Questionnodes[i].firstChild.nodeValue;
var target = document.getElementById("targetDiv1");
target.innerHTML=displayText1;
var displayText2 =
Answernodes[i].firstChild.nodeValue;
var target = document.getElementById("targetDiv2");
target.innerHTML=displayText2;
}
</script>
Right now, I do not know if I am able to alter this code to get the function I want. I have tried parsing an XML file into a javascript array (and then randomly select and remove an element) but have gotten nowhere atm. If anyone has a few suggestions, I would be most grateful. Once again, I want a function that can randomly select a question & answer element from an XML file, but only show it to the user ONCE.
Cheers guys. (sorry this was so long-winded).
write a class with a var hasbeenshown, hasbeenanswered, useranswer, function getquestion, function getanswer.
the instanciated classes, filled with values from your xml file on load you can add to an array and use your random number to choose a random question.
here is a link to an example of how I would do what you're trying to : http://jsfiddle.net/dievardump/xL5mg/4/
I commented some parts of the code and I 'simulate' your getCategory method.
Note : From my code, I think what didn't manage to do is the 'pickRandom' method. I think you have almost all you need to do other parts.
What I do in my code :
I have a collection of Question and Answers
I have a QuestionAndAnswer constructor
When the server result come, I 'parse' the xml file and fill the collection with QuestionAndAnswer objects.
In the HTML is a 'load a question' button. When you click on it, it call the displayQuestion method.
This method picks (get and remove from) a random QandA object from the collection, then display the question and a button to see the associated answer.
Like i said in the comments, i decided to add question the one after the other, but you can change that by having only one question and response handler and modify its content.
Here is the code if one day jsfiddle decide to not work anymore :
Javascript
(function() {
// Question and Answer collection
var oQandACollection = {
collection : [],
length : 0,
// get a QandA object
get : function(i) {
if (i < this.length) {
return this.collection[i];
}
return null;
},
// add a QandA object
add : function(qanda) {
this.collection.push(qanda);
this.length++;
},
// remove a QandA object
remove : function(i) {
if (typeof this.collection[i] !== 'undefined') {
this.collection.splice(i, 1);
this.length--;
}
},
// randomly pick an object from the collection
pickRandom : function() {
if (this.length === 0) return null; // return null if there is no object in the collection
var i = Math.floor(Math.random() * this.length);
var qanda = this.get(i);
this.remove(i);
return qanda;
}
};
// Question and Answer Object
var QandA = function(xmlNode) {
var question = xmlNode.getElementsByTagName('Question')[0] || null;
var answer = xmlNode.getElementsByTagName('Answer')[0] || null;
if (question && answer) {
this.question = question.textContent;
this.answer = answer.textContent;
} else {
return null;
}
};
// function to use as ajax request handler
function fillQandA(xmlDoc) {
// get all WordQuestions elements
var wrappers = xmlDoc.getElementsByTagName('WordQuestions');
for(var i = 0, l = wrappers.length, qanda = null; i < l; i++) {
// create a new question from the current wrapper
// we could have perfom the getElementsByTagName('Question') here
// but since the code is really specific to your example i putted it in the constructor of QandA
// You can change it
qanda = new QandA(wrappers[i]);
if (qanda) {
oQandACollection.add(qanda);
}
}
};
var eList = document.getElementById('qa-list'),
eLoadQuestion = document.getElementById('load-qanda');
// functions to display a question
// i choosed to add question the one after the others,
// so i re-create html elements at every display
// but you also can modify it to have just one question at a time
// matter of choice
function displayQuestion() {
var qanda = oQandACollection.pickRandom(); // get a question
if (qanda) { // if there is a question
// create elements
var eQuestion = document.createElement('p'),
eAnswer = document.createElement('p'),
eBtn = document.createElement('button');
eQuestion.textContent = qanda.question;
eAnswer.textContent = qanda.answer;
eQuestion.classNAme += ' question';
eAnswer.className += ' answer';
eBtn.textContent = 'Show Answer';
// add click event listener to the button to show the answer
eBtn.addEventListener('click', function() {
eAnswer.style.display = 'block';
eList.removeChild(eBtn);
}, false);
eList.appendChild(eQuestion);
eList.appendChild(eAnswer);
eList.appendChild(eBtn);
}
};
// add click event handler on display
eLoadQuestion.addEventListener('click', displayQuestion, false);
// simulate xhr request
function getCategory() {
// fill fake Q&A xml document
var xmlDoc = document.createElement('root');
for(var i = 0, wrapper = null, question = null, answer = null; i < 10; i++) {
wrapper = document.createElement('WordQuestions');
question = document.createElement('Question');
question.textContent = 'Question ' + (i+1);
answer = document.createElement('Answer');
answer.textContent = 'Answer ' + (i+1);
wrapper.appendChild(question);
wrapper.appendChild(answer);
xmlDoc.appendChild(wrapper);
}
// us as xhr request handler like : fillQandA(XMLHttpRequestObject.responseXML);
fillQandA(xmlDoc);
}
getCategory();
// test function
function test() {
for(var i = oQandACollection.length; i--; ) {
displayQuestion();
}
}
//test();
})();
HTML
<div class="wrapper">
<div id="qa-list">
</div>
<button id="load-qanda" value="Load new question">Load new question</button>
</div>
CSS
.wrapper {
width : 500px;
}
.wrapper > div {
position : relative;
width : 100%
}
.answer {
display : none;
}

Categories