Knockout.js format date item - html

In my view I wish to display a knockout.js binded field that contains a date. It is just a display field and not an input field. Something like below when basemodel.actionDate = ko.observable()
<p class="display-field" data-bind="text: baseModel.actionDate"/>
However this is displayed as follows:
2013-06-17T11:56:18.4537687Z
What is the easiest way to format this dd mm yyyy. eg: 17 June 2013?

I recommend the moment.js date formatting library.
Using it, you can do something like this in your view:
<p class="display-field" data-bind="text: moment(baseModel.actionDate()).format('LL')"/>

Either make sure the service output it in a correct format, or format in client side
If you want todo it client side then you can parse the ISO date string into a Date object and then use jQuery globalize to format it to desired format.
I use KO extenders for this
http://jsfiddle.net/vRf5B/42/
ko.extenders.date = function(target, format) {
return ko.computed({
read: function() {
var value = target();
if(typeof value === "string") {
value = new Date(value);
}
format = typeof format === "string" ? format: undefined;
value = Globalize.format(value, format);
return value;
},
write: target
});
}
update
I got a question on my blog how to retrieve the raw date value
It can be done my exposing the raw value on the computed like
http://jsfiddle.net/vRf5B/91/

Declare format function:
Date.prototype.toFormattedDate = function () {
var dd = this.getDate();
if (dd < 10) dd = '0' + dd;
var mm = this.getMonth() + 1;
if (mm < 10) mm = '0' + mm;
var yyyy = this.getFullYear();
/* change format here */
return String(mm + "/" + dd + "/" + yyyy);
};
And use it with the date strings as:
<span data-bind="text: new Date(TaxAuthority.RegDate).toFormattedDate()"></span>

I had some issues (I think) with the mapping plugin trying to use the extender. Since I'm only displaying dates and not allowing them to be edited I prefer to just use a binding handler like this:
Shipped on <span data-bind="date: shipDt"></span>
Here's the handler:
ko.bindingHandlers.date =
{
update: function (element, valueAccessor: () => any, allBindingsAccessor: any)
{
return ko.bindingHandlers.text.update(element, function ()
{
var value: any = ko.utils.unwrapObservable(valueAccessor());
if (value == null)
{
return null;
}
if (typeof value === "string")
{
value = new Date(value);
}
return value.toShortDateString();
}, allBindingsAccessor, null, null);
}
};
I chose to add a prototype to Date object like this (and call toShortDateString on the Date object created in the handler)- but you can replace the logic above with Globalize or whatever you prefer.
Date.prototype.toShortDateString = function ()
{
return (this.getMonth() + 1) + "/" + this.getDate() + "/" + this.getFullYear();
};

If you are referencing moment.js then I would actually format in the knockout model.
var BiographicViewModel = function (person) {
this.FirstName = ko.observable(person.first_name);
this.LastName = ko.observable(person.last_name);
this.DOB = ko.observable(moment(person.birth_date).format("MM/DD/YYYY"));
this.Gender = ko.observable(person.gender);
this.Country = ko.observable(person.country);
this.City = ko.observable(person.city);
};

Related

As3 set set end-Date using value in array

i want to make end-subscription after admin enter the value/date into text_box then it will push to array,after that i want when time reach the date that been enter the application will exit.
But I don't know the method.
below is my code
var my_date:Date;
var my_timer:Timer=new Timer(1000);
my_timer.addEventListener(TimerEvent.TIMER, onTimer);
my_timer.start();
var tarikh:Array = new Array();
tarikh.hari = [0];
tarikh.bulan = [1];
tarikh.tahun = [2];
my_date = new Date();
function onTimer(e:TimerEvent):void
{
my_date = new Date();
//trace(my_date.date + ":" + my_date.month + " : " + my_date.fullYear );
}
btnCuba.addEventListener(MouseEvent.CLICK,MMM);
function MMM (event:MouseEvent):void
{
tarikh.hari = String(hari.text);
tarikh.bulan = String(bulan.text);
tarikh.tahun = String(tahun.text) ;
tarikh.push(hari,bulan,tahun);
setHours();
trace(tarikh.hari + "" + tarikh.bulan + "" + tarikh.tahun);
}
function setHours():void
{
if (my_date.date == tarikh.hari && my_date.month == tarikh.hari && my_date.fullYear == tarikh.hari && && my_date.minutes == 01)
{
NativeApplication.nativeApplication.exit();
}
else
{
my_timer.start();
}
}
tarikh is an array. Arrays are assigned to using the array index access operator. When you want to store data in an array, you do it like this:
array_name[index] = value
or in this case,
tarikh[0] = hari;
Looking at the code, tarikh seems to mean date, hari, bulan and tahun seem to mean day, month and year. The Date class doesn't have these properties (you'll have to translate them to English); alternatively, an easier method of comparing dates is just to compare the getTime() function returns by the two date objects:
if(my_date.getTime() == tarikh.getTime()) { NativeApplication.nativeApplication.exit(); }
The problem is that tarikh is not a Date object now, but is an Array. Arrays don't have any properties by themselves (apart from length), so maybe you're looking for a tarikh being a Date object? You could try making tarikh as a Date object with the values like so:
tarikh = new Date(hari.text + " " + bulan.text + " " + tahun.text); if the text values are letters (like Tue Feb 3 2005), otherwise if they are numbers or integers then try casting it and using it in the appropriate parameter.

Export multiple html tables to Excel

I've scavenged the inter web for answers and though I found some, they were mostly incomplete or not working.
What I'm trying to do is: I have a info page which displays information about a customer or server (or something else), this information is displayed in a table, sometimes multiple tables (I sometimes create my own table for some of the data and use Html.Grid(Model.list) to create tables for the rest of the data stored in lists, all on 1 page).
I found this website which is an awesome: http://www.excelmashup.com/ and does exactly what I want for 1 table, though I need this for multiple tables (they must all be in the same Excel file). I know I can create multiple files (1 for each table) but this is not the desired output.
So I kept on searching and I found a post on stackoverflow: Export multiple HTML tables to Excel with JavaScript function
This seemed promising so I tried using it but the code had some minor errors which I tried to fix:
var tableToExcel = (function () {
var uri = 'data:application/vnd.ms-excel;base64,'
, template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>'
, base64 = function (s) { return window.btoa(unescape(encodeURIComponent(s))) }
, format = function (s, c) { return s.replace(/{(\w+)}/g, function (m, p) { return c[p]; }) }
return function (table, name) {
if (!table.nodeType) table = document.getElementById(table)
var ctx = { worksheet: name || 'Worksheet', table: table.innerHTML }
window.location.href = uri + base64(format(template, ctx))
}
})()
The button I use to trigger it:
<input type="button" onclick="tableToExcel('InformatieTable', 'W3C Example Table')" value="Export to Excel">
but alas to no avail (I did not know what to do with the if (!table.nodeType) table = table line so I just commented it since it seemed to do nothing special).
Now I get an error, or well not really an error but this is what it says when I try to run this code:
Resource interpreted as Document but transferred with MIME type application/vnd.ms-excel: "data:application/vnd.ms-excel;base64,PGh0bWwgeG1sbnM6bz0idXJuOnNjaGVtYXMtbW…JzZXQ9VVRGLTgiLz48L2hlYWQ+PGJvZHk+PHRhYmxlPjwvdGFibGU+PC9ib2R5PjwvaHRtbD4=".
And I get an Excel file as download in my browser but when I try to open it I get an error about the content and file extension not matching and if I would still like to open it. So if I click ok it opens a empty Excel sheet and that's it.
I am currently trying to fix that error, though i don't think it will make any difference to the content of the Excel file.
Is there anyone that can help me fix this? Or provide an other way of doing this?
I do prefer it to be run client side (so jQuery/java) instead of server side to minimize server load.
EDIT
I've found a better example of the jQuery (one that does work) on http://www.codeproject.com/Tips/755203/Export-HTML-table-to-Excel-With-CSS
This converts 1 table into an excel file which is obviously not good enough. But now I have the code to do this so I should be able to adapt it to loop trough all tables on the web page.
Also updated the code in this example to the correct version I'm using now.
I also still get the same error yet when I click on ok when trying to open the Excel file it does show me the content of the table, so I'm just ignoring that for now. anyone who has a solution for this please share.
Thanks to #Axel Richter I got my answer, he reffered me to the following question
I have adapted the code a bit so it would Take all the tables on the web page so it now looks like this:
<script type="text/javascript">
var tablesToExcel = (function () {
var uri = 'data:application/vnd.ms-excel;base64,'
, tmplWorkbookXML = '<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?><Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">'
+ '<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"><Author>Axel Richter</Author><Created>{created}</Created></DocumentProperties>'
+ '<Styles>'
+ '<Style ss:ID="Currency"><NumberFormat ss:Format="Currency"></NumberFormat></Style>'
+ '<Style ss:ID="Date"><NumberFormat ss:Format="Medium Date"></NumberFormat></Style>'
+ '</Styles>'
+ '{worksheets}</Workbook>'
, tmplWorksheetXML = '<Worksheet ss:Name="{nameWS}"><Table>{rows}</Table></Worksheet>'
, tmplCellXML = '<Cell{attributeStyleID}{attributeFormula}><Data ss:Type="{nameType}">{data}</Data></Cell>'
, base64 = function (s) { return window.btoa(unescape(encodeURIComponent(s))) }
, format = function (s, c) { return s.replace(/{(\w+)}/g, function (m, p) { return c[p]; }) }
return function (wsnames, wbname, appname) {
var ctx = "";
var workbookXML = "";
var worksheetsXML = "";
var rowsXML = "";
var tables = $('table');
for (var i = 0; i < tables.length; i++) {
for (var j = 0; j < tables[i].rows.length; j++) {
rowsXML += '<Row>'
for (var k = 0; k < tables[i].rows[j].cells.length; k++) {
var dataType = tables[i].rows[j].cells[k].getAttribute("data-type");
var dataStyle = tables[i].rows[j].cells[k].getAttribute("data-style");
var dataValue = tables[i].rows[j].cells[k].getAttribute("data-value");
dataValue = (dataValue) ? dataValue : tables[i].rows[j].cells[k].innerHTML;
var dataFormula = tables[i].rows[j].cells[k].getAttribute("data-formula");
dataFormula = (dataFormula) ? dataFormula : (appname == 'Calc' && dataType == 'DateTime') ? dataValue : null;
ctx = {
attributeStyleID: (dataStyle == 'Currency' || dataStyle == 'Date') ? ' ss:StyleID="' + dataStyle + '"' : ''
, nameType: (dataType == 'Number' || dataType == 'DateTime' || dataType == 'Boolean' || dataType == 'Error') ? dataType : 'String'
, data: (dataFormula) ? '' : dataValue.replace('<br>', '')
, attributeFormula: (dataFormula) ? ' ss:Formula="' + dataFormula + '"' : ''
};
rowsXML += format(tmplCellXML, ctx);
}
rowsXML += '</Row>'
}
ctx = { rows: rowsXML, nameWS: wsnames[i] || 'Sheet' + i };
worksheetsXML += format(tmplWorksheetXML, ctx);
rowsXML = "";
}
ctx = { created: (new Date()).getTime(), worksheets: worksheetsXML };
workbookXML = format(tmplWorkbookXML, ctx);
console.log(workbookXML);
var link = document.createElement("A");
link.href = uri + base64(workbookXML);
link.download = wbname || 'Workbook.xls';
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
})();
</script>
so now when ever I want a page to have an option to be exported to excel i add a refference to that script and i add the following button to my page:
<button onclick="tablesToExcel(['ServerInformatie', 'Relaties'], 'VirtueleMachineInfo.xls', 'Excel')">Export to Excel</button>
so the method:
tablesToExcel(WorksheetNames, fileName, 'Excel')
Where worksheetNames is an array which needs to contain as much names (or more) as there are tables on the page. You could ofcourse chose to create the worksheet names in a different way.
And where fileName is ofcourse the name of the file you'll be downloading.
Not having it all in 1 worksheet is a shame but at least this will do for now.
Here is the code that I used to put multiple HTML tables in the same Excel sheet:
import TableExport from 'tableexport';
const tbOptions = {
formats: ["xlsx"], // (String[]), filetype(s) for the export, (default: ['xlsx', 'csv', 'txt'])
bootstrap: true, // (Boolean), style buttons using bootstrap, (default: true)
exportButtons: false, // (Boolean), automatically generate the built-in export buttons for each of the specified formats (default: true)
position: "bottom", // (top, bottom), position of the caption element relative to table, (default: 'bottom')
}
DowlandExcel = (key) => {
const table = TableExport(document.getElementById(key), tbOptions);
var exportData = table.getExportData();
var xlsxData = exportData[key].xlsx;
console.log(xlsxData); // Replace with the kind of file you want from the exportData
table.export2file(xlsxData.data, xlsxData.mimeType, xlsxData.filename, xlsxData.fileExtension, xlsxData.merges, xlsxData.RTL, xlsxData.sheetname)
}
DowlandExcelMultiTable = (keys) => {
const tables = []
const xlsxDatas = []
keys.forEach(key => {
const selector = document.getElementById(key);
if (selector) {
const table = TableExport(selector, tbOptions);
tables.push(table);
xlsxDatas.push(table.getExportData()[key].xlsx)
}
});
const mergeXlsxData = {
RTL: false,
data: [],
fileExtension: ".xlsx",
filename: 'rapor',
merges: [],
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
sheetname: "Rapor"
}
for (let i = 0; i < xlsxDatas.length; i++) {
const xlsxData = xlsxDatas[i];
mergeXlsxData.data.push(...xlsxData.data)
xlsxData.merges = xlsxData.merges.map(merge => {
const diff = mergeXlsxData.data.length - xlsxData.data.length;
merge.e.r += diff;
merge.s.r += diff;
return merge
});
mergeXlsxData.merges.push(...xlsxData.merges)
mergeXlsxData.data.push([null]);
}
console.log(mergeXlsxData);
tables[0].export2file(mergeXlsxData.data, mergeXlsxData.mimeType, mergeXlsxData.filename, mergeXlsxData.fileExtension, mergeXlsxData.merges, mergeXlsxData.RTL, mergeXlsxData.sheetname)
}

Funky IE JSON conversions

When running our AngularJS app in IE11 everything looks great in the debugger, but when our app encodes the data as JSON to save to our database, we get bad results.
Our app obtains a record from our database, then some manipulation is done and then the data is saved back to the server from another model.
Here is the data I got back from the server in the setAttendanceGetSInfo() function below:
{"data":{"Start":"2014-10-16T19:36:00Z","End":"2014-10-16T19:37:00Z"},
This is the code used to "convert the data" to 3 properties in our model:
var setAttendanceGetSInfo = function (CourseId, PID) {
return setAttendanceInfo(CourseId, PID)
.then(function (result) {
return $q.all([
$http.get("../api/Axtra/getSInfo/" + model.event.Id),
$http.get("../api/Axtra/GetStartAndEndDateTime/" + aRow.Rid)
]);
}).then(function (result) {
var r = result.data;
var e = Date.fromISO(r.Start);
var f = Date.fromISO(r.End);
angular.extend(model.event, {
examDate: new Date(e).toLocaleDateString(),
examStartTime: (new Date(e)).toLocaleTimeString(),
examEndTime: (new Date(f)).toLocaleTimeString()
});
return result.sInfo;
});
};
fromISO is defined as:
(function(){
var D= new Date('2011-06-02T09:34:29+02:00');
if(!D || +D!== 1307000069000){
Date.fromISO= function(s){
var day, tz,
rx=/^(\d{4}\-\d\d\-\d\d([tT ][\d:\.]*)?)([zZ]|([+\-])(\d\d):(\d\d))?$/,
p= rx.exec(s) || [];
if(p[1]){
day= p[1].split(/\D/);
for(var i= 0, L= day.length; i<L; i++){
day[i]= parseInt(day[i], 10) || 0;
};
day[1]-= 1;
day= new Date(Date.UTC.apply(Date, day));
if(!day.getDate()) return NaN;
if(p[5]){
tz= (parseInt(p[5], 10)*60);
if(p[6]) tz+= parseInt(p[6], 10);
if(p[4]== '+') tz*= -1;
if(tz) day.setUTCMinutes(day.getUTCMinutes()+ tz);
}
return day;
}
return NaN;
}
}
else{
Date.fromISO= function(s){
return new Date(s);
}
}
})()
Take a look at the screenshot of the event model data:
But, if I eval the event model using JSON.stringify(model.event), I get this:
{\"examDate\":\"?10?/?16?/?2014\",\"examStartTime\":\"?2?:?44?:?00? ?PM\",\"examEndTime\":\"?2?:?44?:?00? ?PM\"}
And this is the JSON encoded data that actually got stored on the DB:
"examDate":"¿10¿/¿16¿/¿2014","examStartTime":"¿2¿:¿36¿:¿00¿ ¿PM","examEndTime":"¿2¿:¿37¿:¿00¿ ¿PM"
What is wrong here and how can I fix this? It works exactly as designed in Chrome and Firefox. I have not yet tested on Safari or earlier versions of IE.
The toJSON for the date class isn't defined perfectly the same for all browsers.
(You can see a related question here: Discrepancy in JSON.stringify of date values in different browsers
I would suspect that you have a custom toJSON added to the Date prototype since your date string doesn't match the standard and that is likely where your issue is. Alternatively, you can use the Date toJSON recommended in the above post to solve your issues.
First, I modified the fromISO prototype to this:
(function () {
var D = new Date('2011-06-02T09:34:29+02:00');
if (!D || +D !== 1307000069000) {
Date.fromISO = function (s) {
var D, M = [], hm, min = 0, d2,
Rx = /([\d:]+)(\.\d+)?(Z|(([+\-])(\d\d):(\d\d))?)?$/;
D = s.substring(0, 10).split('-');
if (s.length > 11) {
M = s.substring(11).match(Rx) || [];
if (M[1]) D = D.concat(M[1].split(':'));
if (M[2]) D.push(Math.round(M[2] * 1000));// msec
}
for (var i = 0, L = D.length; i < L; i++) {
D[i] = parseInt(D[i], 10);
}
D[1] -= 1;
while (D.length < 6) D.push(0);
if (M[4]) {
min = parseInt(M[6]) * 60 + parseInt(M[7], 10);// timezone not UTC
if (M[5] == '+') min *= -1;
}
try {
d2 = Date.fromUTCArray(D);
if (min) d2.setUTCMinutes(d2.getUTCMinutes() + min);
}
catch (er) {
// bad input
}
return d2;
}
}
else {
Date.fromISO = function (s) {
return new Date(s);
}
}
Date.fromUTCArray = function (A) {
var D = new Date;
while (A.length < 7) A.push(0);
var T = A.splice(3, A.length);
D.setUTCFullYear.apply(D, A);
D.setUTCHours.apply(D, T);
return D;
}
Date.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
})()
Then I added moment.js and formatted the dates when they get stored:
var SaveAffRow = function () {
// make sure dates on coursedate and event are correct.
var cd = model.a.courseDate;
var ed = model.event.examDate;
var est = model.event.examStartTime;
var eet = model.event.examEndTime;
model.a.courseDate = moment(cd).format("MM/DD/YYYY");
model.event.examDate = moment(ed).format("MM/DD/YYYY");
model.event.examStartTime = moment(est).format("MM/DD/YYYY hh:mm A");
model.event.examEndTime = moment(eet).format("MM/DD/YYYY hh:mm A");
affRow.DocumentsJson = angular.toJson({a: model.a, event: model.event});
var aff = {};
if (affRow.Id != 0)
aff = affRow.$update({ Id: affRow.Id });
else
aff = affRow.$save({ Id: affRow.Id });
return aff;
};
and when they get read (just in case they are messed up already):
var setAttendanceGetSInfo = function (CourseId, PID) {
return setAttendanceInfo(CourseId, PID)
.then(function (result) {
return $q.all([
$http.get("../api/Axtra/getSInfo/" + model.event.Id),
$http.get("../api/Axtra/GetStartAndEndDateTime/" + aRow.Rid)
]);
}).then(function (result) {
var r = result.data;
var e = Date.fromISO(r.Start);
var f = Date.fromISO(r.End);
angular.extend(model.event, {
examDate: moment(e).format("MM/DD/YYYY"),
examStartTime: moment(e).format("MM/DD/YYYY hh:mm A"),
examEndTime: moment(f).format("MM/DD/YYYY hh:mm A")
});
return result.sInfo;
});
};

Parsing and formatting JSON date variable

I am parsing a JSON object with JavaScript and displaying it in a styled HTML table.
The date variable comes as a long string. (e.g. 2011-11-23 10:21:49.695805)
How can I format the date so as to be displayed: year-month-day, hour-minute instead of 2011-11-23 10:21?
The AJAX call:
$$.ajax({
url: '/ajax/myfolder',
dataType:"json",
success: function(json) {
$$('#JsonGrid').append(CreateTableView(json, "jsonFeed", true)).fadeIn();
parseDate();
}
});
function parseDate() {
}
The json object:
[{"status": "success", "date": "2011-11-23 10:21:49.695805", "user": "xy", "jobname": "jobnamexyz"}]
You can use date functions, DateJS, etc., but if the format is fixed as in your example you can just take the first sixteen characters:
var date = json["date"].substr(0,16);
EDIT: sorry, just noticed you wanted a comma in there. So:
var date = json["date"].substr(0,10) + ", " + json["date"].substr(11,5);
EDIT 2: OK, try moving the call to parseDate() to before you call CreateTableView(), so you can "fix" the data before it is used. The following assumes the json is an array of objects:
success: function(json) {
parseDate(json);
$$('#JsonGrid').append(CreateTableView(json, "jsonFeed", true)).fadeIn();
function parseDate(data) {
for (var i=0; i<data.length; i++)
if (data[i]["date"])
data[i]["date"] = data[i]["date"].substr(0,10)
+ ", " + data[i]["date"].substr(11,5);
}
You can use String.match method to parse the parts of string like "2011-11-23 10:21:49.695805". For example the code
var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
"([T| ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
"(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
var dateString = "2011-11-23 10:21:49.695805";
var d = dateString.match(new RegExp(regexp));
will initialize d as array where d[1], d[3], d[5], d[7], d[8] and d[10] are year (2011), month (11), day (23), hour (10), minutes (21), seconds (49). The d[12] will be 695805 and only the first tree digits of it are milliseconds. So you can either create a Date like
var date = new Date(d[1],d[3],d[5],d[7],d[8],d[10],d[12].substr(0,3));
or display the parsed date as another string in any other format which you prefer.
$$.ajax({
url: '/ajax/myfolder',
dataType:"json",
success: function(json) {
$$('#JsonGrid').append(CreateTableView(json, "jsonFeed", true)).fadeIn();
parseDate(json);
}
});
function parseDate(json) {
var arr = json["date"].split(/:/);
var date = arr[0] + ":"+arr[1];
alert(date);
return date;
}
Maybe this jQuery extension can help you out: http://momentjs.com/
Like:
var now = moment();
var formatted = now.format('dddd, MMMM Do YYYY');

CRM 2011: Getting entity with Javascript

I am working on some CRM 2011 Online customisations and I need to get an entity using javascript.
The entity I need will be based on the ID value of another field (a Contact entity) - this Contact ID I can get fine.
The entity I want is a custom entity. There may be multiple matches based on the Contact ID so I just want to get the first one in the list (order not important)
So far I have looked into a few ways to do this...
OData - I couldn't find enough examples on this as to what query expressions I can create, also I don't know if/how to make this work for custom entities
FetchXML - I can create a nice FetchXML query using the built-in "advanced find" too and would be happy to call this from javascript if anyone can help? I found one promising answer here but I could not see how the "results" return data was being set (Service.Fetch function)
SOAP Request - First thing I tried is a similar method as I could have done in CRM 4 but this does not seem to work. Although the request executes, my result data just seems to be empty. This is all I have code for so if any one can spot a problem with the code below then that would be great.
EDIT: I have spotted some redundant query data (I had removed link opening tags but left closing tags) - since removing this I now get XML result data... however, the where clause does not seem to apply (just get list of all entities)
var xml = "<?xml version='1.0' encoding='utf-8'?>" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
GenerateAuthenticationHeader() +
"<soap:Body>" +
"<RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
"<query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
"<q1:EntityName>new_vehicle</q1:EntityName>" +
"<q1:ColumnSet xsi:type='q1:ColumnSet'>" +
"<q1:Attributes>" +
"<q1:Attribute>new_vehicleid</q1:Attribute>" +
"<q1:Attribute>new_primarydriver</q1:Attribute>" +
"<q1:Attribute>statuscode</q1:Attribute>" +
"<q1:Attribute>new_registration</q1:Attribute>" +
"</q1:Attributes>" +
"</q1:ColumnSet>" +
"<q1:Distinct>false</q1:Distinct>" +
"<q1:Conditions>" +
"<q1:Condition>" +
"<q1:AttributeName>new_primarydriver</q1:AttributeName>" +
"<q1:Operator>Equal</q1:Operator>" +
"<q1:Values>" +
"<q1:Value xmlns:q2='http://microsoft.com/wsdl/types/' xsi:type='q2:guid'>" +
customerID +
"</q1:Value></q1:Values></q1:Condition>" +
"</q1:Conditions>" +
"</query></RetrieveMultiple>" +
"</soap:Body></soap:Envelope>";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var result = xmlHttpRequest.responseXML.xml;
var doc = new ActiveXObject("MSXML2.DOMDocument");
doc.async = false;
doc.loadXML(result);
var id = doc.selectSingleNode("//new_vehicleid");
var registration = doc.selectSingleNode("//new_registration");
if(id == null)
return null;
var vehicle = new Array();
value[0] = new Object();
value[0].id = id;
value[0].name = registration;
value[0].entityType = "new_vehicle";
return vehicle;
Sorry about the big code post but hopefully somebody who has a better understanding can help
Firstly, thanks to GlennFerrieLive for his answer post. The samples I found with the Dynamics CRM 2011 SDK (well just one in particular) really helped and the JSON parser included was perfect for the job!
I am posting this answer to give a full example of how I did it with some important comments to pay attention to which may not be so obvious from the SDK examples.
Get selected ID value from lookup field
The aim of my task was to use javascript to get set a lookup field, based on the selected data of another lookup entity. The entity to set is "new_vehicle" and the entity to query on is "customer".
First job is to get the ID value of the contact lookup field...
var customerItem = Xrm.Page.getAttribute("customerid").getValue();
var customerID = customerItem[0].id;
Querying an entity using an ID
Next is the part where I used the customerID value to find the vehicle that is currently assigned to them (the entity I want to use to set a lookup field).
First problem I found was that when querying with OData, the ID value does not seem to work with curly brackets {} - so these need to be removed...
customerID = customerID.replace('{', '').replace('}', '');
Next we get the oDataPath...
var oDataPath = Xrm.Page.context.getServerUrl() + "/xrmservices/2011/organizationdata.svc";
Then we can construct the OData query...
var filter = "/new_vehicleSet?" +
"$select=new_vehicleId,new_Registration" +
"&$filter=new_PrimaryDriver/Id eq (guid'" + customerID + "')" +
"&$orderby=new_LastAllocationDate desc" +
"&$top=1";
NOTE: There are a couple of important things to note here...
When using a guid value you must explicitly say it is a guid using (guid'xxx')
When filtering by a lookup entity (e.g. new_PrimaryDriver) you must append the value to query (e.g. Id) - this results in new_PrimaryDriver/Id
Once we have the query setup we can request the data as follows...
var retrieveRecordsReq = new XMLHttpRequest();
retrieveRecordsReq.open("GET", oDataPath + filter, true);
retrieveRecordsReq.setRequestHeader("Accept", "application/json");
retrieveRecordsReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
retrieveRecordsReq.onreadystatechange = function () {
if (this.readyState == 4) {
if (this.status == 200) {
var retrievedRecords = JSON.parse(retrieveRecordsReq.responseText).d;
if(retrievedRecords.results.length > 0)
{
var vehicle = retrievedRecords.results[0];
SetLookup("new_replacedvehicle", vehicle.new_vehicleId, vehicle.new_Registration, "new_vehicle");
}
}
}
};
retrieveRecordsReq.send();
Note that this is an asynchronous call and the onreadystatechange function will be processed upon completion, in this function we do a couple of checks to see if it was a success and the we parse the resulting JSON data - the JSON.Parse function has been included at the bottom of this post (but is available from the SDK)
Setting a lookup field using the entity queried above
The other function to make note of here is SetLookup which is just a simple helper function I added to set a lookup field. This is as follows...
function SetLookup(fieldName, idValue, textValue, typeValue)
{
var value = new Array();
value[0] = new Object();
value[0].id = idValue;
value[0].name = textValue;
value[0].typename = typeValue;
Xrm.Page.getAttribute(fieldName).setValue(value);
}
JSON parse function
This is the JSON helper function that was used in the above code (JSON.parse), pasted as it was found in the SDK...
if (!this.JSON) { this.JSON = {}; } (function () { function f(n) { return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf(); }; } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' }, rep; function quote(string) { escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { var i, k, v, length, mind = gap, partial, value = holder[key]; if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } if (typeof rep === 'function') { value = rep.call(holder, key, value); } switch (typeof value) { case 'string': return quote(value); case 'number': return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': return String(value); case 'object': if (!value) { return 'null'; } gap += indent; partial = []; if (Object.prototype.toString.apply(value) === '[object Array]') { length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { k = rep[i]; if (typeof k === 'string') { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { var i; gap = ''; indent = ''; if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } } else if (typeof space === 'string') { indent = space; } rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } return str('', { '': value }); }; } if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { var j; function walk(holder, key) { var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '#').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { j = eval('(' + text + ')'); return typeof reviver === 'function' ? walk({ '': j }, '') : j; } throw new SyntaxError('JSON.parse'); }; } } ());