I have a MultiSelectDropDown, that is, several RadComboBox controls are used in a combined way. For example, I can have a dropdown for regions, another for depots and another for user. The idea is to change the content of lower levels dynamically whenever items are selected or unselected on a higher level. The problem is that in the case when many items are selected, this becomes brutally slow due to some Telerik functions, but I do not understand why. This is a chunk from the client-side of the MultiSelectDropDown prototype:
changeLowerLevels: function (valueIndex, values, value) {
if (!this.canChange) return;
//Get selected values from combobox
var combo = $find(this.ddlIDs[valueIndex - 1]);
var cbItems = combo.get_checkedItems();
var selectedItems = [];
var change = null;
var counter = 0;
if (cbItems.length) this.filterString = "";
for (var i = 0; i < cbItems.length; i++) {
counter++;
if (this.filterString == "") this.filterString = cbItems[i].get_text();
selectedItems.push(cbItems[i].get_value());
}
if (counter > 1) this.filterString += " with " + (counter - 1) + " other" + ((counter > 2) ? "s" : "");
if (JSON.stringify(selectedItems) === JSON.stringify(this.selectedItems[valueIndex - 1]) || selectedItems == [])
return;
this.selectedItems[valueIndex - 1] = selectedItems;
var controlObject = this;
var combo = $find(this.ddlIDs[valueIndex]);
var comboItems = combo.get_items();
if(!this.disabled) combo.enable();
combo.clearItems();
if (valueIndex == 1) this.twoLevelCache = values;
var val = values;
//break if all items are found
var nrOfSelectedItems = this.selectedItems[valueIndex - 1].length;
var nrOfFoundItems = 0;
var index = 0;
var indexes = [];
var found = false;
while (nrOfFoundItems < nrOfSelectedItems && val[index] !== undefined) {
found = (this.selectedItems[valueIndex - 1].indexOf(val[index].Value) != -1);
if (!(found))
index++;
else {
indexes.push(index)
nrOfFoundItems++;
index++;
}
}
//separators from valuesIndex - 1 level
var controlObject = this;
for (var i = 0; i < indexes.length; i++) {
var separator = new Telerik.Web.UI.RadComboBoxItem();
separator.set_text("<span><a class=\"checkAll tt-multi-uncheck-icon\" index=\"" + index + "\">U</a>" + $find(this.ddlIDs[valueIndex - 1]).findItemByValue(val[indexes[i]].Value).get_text() + "</span>");
separator.set_value("");
separator.set_isSeparator(true);
comboItems.add(separator);
this.twoLevelCache.push(val[indexes[i]].Levels);
//valuesIndex level
var valuesArray = val;
var comboItem = new Telerik.Web.UI.RadComboBoxItem();
for (var depot in valuesArray[indexes[i]].Levels) {
comboItem = new Telerik.Web.UI.RadComboBoxItem();
comboItem.set_text(valuesArray[indexes[i]].Levels[depot].Name);
comboItem.set_value(valuesArray[indexes[i]].Levels[depot].Value);
comboItems.add(comboItem);
comboItem = null;
}
$('#' + this.ddlIDs[valueIndex] + '_DropDown a.checkAll').unbind().on("click", function () {
checkAllLowerItems(this, controlObject.ddlIDs[valueIndex]);
});
}
combo.set_emptyMessage(this.allText);
//$("#" + this.ddlIDs[valueIndex]).html(returnValue);
if (this.ddlIDs.length > valueIndex + 1) {
var paramToPass = (((val == undefined) || (val[index] === undefined)) ? ("") : (val[index]));
if (this.allText.length > 0)
this.changeLowerLevels(valueIndex + 1, paramToPass, "");
else {
if (paramToPass !== "")
paramToPass = paramToPass.Levels;
if ((val[index] == undefined) || (val[index].Levels[0] === undefined) || (val[index].Levels[0].Value === "")) {
this.changeLowerLevels(valueIndex + 1, paramToPass, "");
}
else {
this.changeLowerLevels(valueIndex + 1, paramToPass, val[index].Levels[0].Value);
}
}
}
else {
if (this.allText.length > 0)
this.selectedItems[valueIndex] = "";
else
if ((val[index] == undefined) || (val[index].Levels[0] === undefined) || (val[index].Levels[0].Value === "")) {
this.selectedItems[valueIndex] = "";
}
else {
this.selectedItems[valueIndex] = val[index].Levels[0].Value;
}
}
this.setText();
}
combo.clearItems() is extremeley slow. I have take a look on how it is implemented:
function (){var f=this._parent._getControl();?if(f._checkBoxes){f._checkedIndicesJson="[]";?f._checkedIndices=[];?var g=f.get_items();?for(var d=0,e=g.get_count();?d<e;?d++){var c=f.get_items().getItem(d);?c.set_checked(false);?}f.updateClientState();?}a.RadComboBoxItemCollection.callBaseMethod(this,"clear");?}
How can I make sure that this Javascript function speeds up?
I have finally solved the problem by rewriting Telerik client-side functionalities. It was a long and difficult debugging, but it yielded a large performance boost in the most difficult circumstances. From ~30 000 milliseconds, to ~300. Let's see the parts of the optimization:
The actual rewrite
/* Overriding Telerik functions Start */
var overridenTelerikControls = false;
function overrideTelerikFunctionalities() {
if (!overridenTelerikControls) {
overridenTelerikControls = true;
Telerik.Web.UI.RadComboBox.prototype.clearItems = function (isMultiSelectDropDown) {
this.get_items().clear(isMultiSelectDropDown);
this._itemData = null;
};
Telerik.Web.UI.RadComboBoxItemCollection.prototype.clear = function (isMultiSelectDropDown){
var f=this._parent._getControl();
if(f._checkBoxes){
f._checkedIndicesJson="[]";
f._checkedIndices=[];
var g = f.get_items();
for(var d=0,e=g.get_count();d<e;d++){
var c=f.get_items().getItem(d);
c.set_checked(false, isMultiSelectDropDown);
}
if (isMultiSelectDropDown) {
f._updateComboBoxText();
if (f._checkAllCheckBoxElement != null) {
f._updateCheckAllState();
}
}
f.updateClientState();
}
Telerik.Web.UI.RadComboBoxItemCollection.callBaseMethod(this, "clear");
};
Telerik.Web.UI.RadComboBoxItem.prototype.set_checked = function (d, isMultiSelectDropDown){
if(!this.get_enabled()){
return;
}
this._setChecked(d);
var c=this.get_comboBox();
if(c){
if(d){
c._registerCheckedIndex(this.get_index());
}else{
c._unregisterCheckedIndex(this.get_index());
}
if (!isMultiSelectDropDown) {
c._updateComboBoxText();
}
if((!isMultiSelectDropDown) && (c._checkAllCheckBoxElement!=null)){
c._updateCheckAllState();
}
}
};
}
}
/* Overriding Telerik functions End*/
My approach was to keep the old way of their working by default, but if an isMultiSelectDropDown parameter is passed, then work in the optimized manners. So we have a switch materialized as a parameter and we can turn it on/off. The main difference was that the old way was to change the label text showing the selected elements each time a checkbox is checked/unchecked. The main improvement was to do this change after all the checkboxes were checked/unchecked. This extremely simple idea was the driving force behind the boost of performance.
Actual usage
overrideTelerikFunctionalities();
combo.clearItems(true);
This was the functionalities were overriden if they were not already and the parameter was true, therefore the new approach was chosen.
Test, test, test
What I am trying to do is add a javascript variable to a click to call href so that whenever the phone number changes from the javascript the click to call href will change as well. The javascript that defines the variable has a default value (ex: 1-800-555-1212), but can change whenever the page loads where the tfid defines a different phone number (ex: http://www.domain.com?tfid=8663337777)
Here's an example of what's there currently:
<a href="tel:<this phone number needs to be dynamic as well>">Call this number: <script language="javascript" >
key = getVar("keyword");
tn = getVar("tfid");
source = getVar("source");
content = getVar("content");
campaign = getVar("campaign");
if(tn!="")
{
setcookie(key,tn);
}
getcookie();
</script></a>
EDIT:
In order to show all elements to how the phone number is shown or switched out, I've included the javascript below that is called whenever a page loads:
// JavaScript Document start
//function to set cookie from the URL
function pixelfire(debug)
{
var phone_number=getVar("phone_number");
var keyword=getVar("keyword");
var source=getVar("source");
if(keyword)
{
//document.write("set phone number "+phone_number);
//document.write("set keyword "+keyword);
setcookie(keyword,phone_number);
//document.write("back from set cookie");
}
else
{
var keyword=get_named_cookie("MM_Keyword");
var phone_number=get_named_cookie("MM_TrackableNumber");
//document.write("retrieved keyword "+keyword);
//document.write("retrieved phone number "+phone_number);
if(keyword)
{
}
else
{
return null;
}
}
if(debug)
{
document.write("here are cookies<BR><P>"+document.cookie);
}
var campaign=getVar("campaign");
var content=getVar("content");
//document.write("location is "+location);
var url="http://www.mongoosemetrics.com/pixelfire.php?phone_number="+phone_number;
var url = url + "&keyword="+keyword;
var url = url + "&source="+source;
var url = url + "&campaign="+campaign;
var url = url + "&content="+content;
//document.write("url is "+ url);
myImage= new Image();
myImage.src=url;
}
function setcookie(key,tn,path){
index = -1;
var today = new Date();
today.setTime( today.getTime() );
var cookie_expire_date = new Date(today.getTime() + (365* 86400000));
document.cookie="MM_TrackableNumber="+tn+";path=/;expires="+cookie_expire_date.toGMTString();
document.cookie="MM_Keyword="+key+";path=/;expires="+cookie_expire_date.toGMTString();
}
//function to retrive the cookie
function getcookie(){
//plcae your default phone number to show in case cookie is not set
defaultphone="8005551212";
if(document.cookie){ //check if there is a cookie set
index = document.cookie.indexOf("MM_TrackableNumber");
if (index != -1){
namestart = (document.cookie.indexOf("=", index) + 1);
nameend = document.cookie.indexOf(";", index);
if (nameend == -1) {nameend = document.cookie.length;}
document.write(formatnumber(document.cookie.substring(namestart, nameend)));
}
else
{
document.write(formatnumber(defaultphone));
}
}
else
{
document.write(formatnumber(defaultphone));
}
}
function get_named_cookie(name)
{
if(document.cookie)
{
index=document.cookie.indexOf(name);
if (index != -1)
{
namestart = (document.cookie.indexOf("=", index) + 1);
nameend = document.cookie.indexOf(";", index);
if (nameend == -1) {nameend = document.cookie.length;}
var ret_one = document.cookie.substring(namestart, nameend);
return ret_one;
}
}
}
//function to format the phonenumber to (123) 456-7890
function formatnumber(num)
{
_return="1-";
var ini = num.substring(0,3);
_return+=ini+"-";
var st = num.substring(3,6);
_return+=st+"-";
var end = num.substring(6,10);
_return+=end;
return _return;
}
function getVar(name)
{
get_string = document.location.search;
return_value = '';
do { //This loop is made to catch all instances of any get variable.
name_index = get_string.indexOf(name + '=');
if(name_index != -1)
{
get_string = get_string.substr(name_index + name.length + 1, get_string.length - name_index);
end_of_value = get_string.indexOf('&');
if(end_of_value != -1)
value = get_string.substr(0, end_of_value);
else
value = get_string;
if(return_value == '' || value == '')
return_value += value;
else
return_value += ', ' + value;
}
} while(name_index != -1)
//Restores all the blank spaces.
space = return_value.indexOf('+');
while(space != -1)
{
return_value = return_value.substr(0, space) + ' ' +
return_value.substr(space + 1, return_value.length);
space = return_value.indexOf('+');
}
return(return_value);
}
/*end*/
As simple as this may sound (which I know it's not), I need the <a> tag to do this: <a href="tel:FILL THIS WITH EITHER THE DEFAULT NUMBER OR THE NEW NUMBER DEFINED BY THE TFID URL">
You maybe want to wrap it in a function?
<a onclick="doSomething('123456789')" href="tel:<this phone number needs to be dynamic as well>">Call this number: </a>
<script language="javascript" >
function doSomething(telNumber){
var tel = telNumber;
key = getVar("keyword");
tn = getVar("tfid");
source = getVar("source");
content = getVar("content");
campaign = getVar("campaign");
if(tn!="")
{
setcookie(key,tn);
}
getcookie();
}
</script>
You can try to store the phonenumber into the ID of the then use this script
<script>
$(document).ready(function () {
$(".clickable").onClick(function() {
var phone = $(this).attr('id')
});
});
</script>
<a class="clickable" id="{variable_phone_number}" href="...."> ... </a>
EDIT: Try this
<a class="clickable" id="+298384858849" href="...."> ... </a>
<script type="text/javascript">
$(document).ready(function () {
var number = $(".clickable").attr('id')
});
</script>
Remember to add the id and the class attributes
The following code is giving me an error in the chrome developer tools:
"uncaught syntax error- unexpected token"
Specifically, the Errors come up in the populate header function at
var notraw = JSON.parse(arrayraw)
and in the first if statement, at
parsed = JSON.parse(retrieved); //var test is now re-loaded!
These errors haven't come up in previous iterations. Does anyone know why?
// This statement should be ran on load, to populate the table
// if Statement to see whether to retrieve or create new
if (localStorage.getItem("Recipe") === null) { // if there was no array found
console.log("There was no array found. Setting new array...");
//Blank Unpopulated array
var blank = [
["Untitled Recipe", 0, 0]
];
// Insert Array into local storage
localStorage.setItem("Recipe", JSON.stringify(blank));
console.log(blank[0]);
} else {
console.log("The Item was retrieved from local storage.");
var retrieved = localStorage.getItem("Recipe"); // Retrieve the item from storage
// test is the storage entry name
parsed = JSON.parse(retrieved); //var test is now re-loaded!
// we had to parse it from json
console.log(parsed)
}
// delete from array and update
function deletefromarray(id) {
var arrayraw = localStorage.getItem("Recipe")
var notraw = JSON.parse(arrayraw)
console.log(notraw[id])
notraw.splice(id, 1);
localStorage.setItem("Recipe", JSON.stringify(notraw));
}
// This adds to local array, and then updates that array.
function addtoarray(ingredient, amount, unit) {
var arrayraw = localStorage.getItem("Recipe")
var notraw = JSON.parse(arrayraw)
notraw.push([ingredient, amount, unit]);
localStorage.setItem("Recipe", JSON.stringify(notraw));
var recipeid = notraw.length - 1
console.log("recipe id:" + recipeid)
}
//The calculation function, that gets the ingredients from the array, and calculates what ingredients are needed
function calculate(multiplier) {
alert("Calculate function was called")
var length = recipearray.length - 1;
console.log("There are " + length + " ingredients.");
for (i = 0; i < length; i++) {
console.log("raw = " + recipearray[i + 1][1] + recipearray[i + 1][2]);
console.log("multiplied = " + recipearray[i + 1][1] / recipearray[0][2] * multiplier + recipearray[i + 1][2]);
}
}
// The save function, This asks the user to input the name and how many people the recipe serves. This information is later passed onto the array.
function save() {
var verified = true;
while (verified) {
var name = prompt("What's the name of the recipe?")
var serves = prompt("How many people does it serve?")
if (serves === "" || name === "" || isNaN(serves) === true || serves === "null") {
alert("You have to enter a name, followed by the number of people it serves. Try again.")
verified = false;
} else {
alert("sucess!");
var element = document.getElementById("header");
element.innerHTML = name;
var header2 = document.getElementById("details");
header2.innerHTML = "Serves " + serves + " people"
calculate(serves)
var arrayraw = localStorage.getItem("Recipe")
var notraw = JSON.parse(arrayraw)
notraw.splice(0, 1, [name, serves, notraw.length])
localStorage.setItem("Recipe", JSON.stringify(notraw))
return;
}
}
}
// the recipe function processes the inputs for the different ingredients and amounts.
function recipe() {
// Declare all variables
var ingredient = document.getElementById("ingredient").value;
var amount = document.getElementById("number").value;
var unit = document.getElementById("unit").value;
var count = "Nothing";
console.log("Processing");
if (isNaN(amount)) {
alert("You have to enter a number in the amount field")
} else if (ingredient === "" || amount === "" || unit === "Select Unit") {
alert("You must fill in all fields.")
} else if (isNaN(ingredient) === false) {
alert("You must enter an ingredient NAME.")
} else {
console.log("hey!")
// console.log(recipearray[1][2] + recipearray[1][1])
var totalamount = amount + unit
edit(ingredient, amount, unit, false)
insRow(ingredient, totalamount) // post(0,*123456*,"Fish")
}
}
function deleteRow(specified) {
// Get the row that the delete button was clicked in, and delete it.
var inside = specified.parentNode.parentNode.rowIndex;
document.getElementById('table').deleteRow(inside);
var rowid = inside + 1 // account for the first one being 0
console.log("An ingredient was deleted by the user: " + rowid);
// Remove this from the array.
deletefromarray(-rowid);
}
function insRow(first, second) {
//var first = document.getElementById('string1').value;
//var second = document.getElementById('string2').value;
// This console.log("insRow: " + first)
// Thisconsole.log("insRow: " + second)
var x = document.getElementById('table').insertRow(0);
var y = x.insertCell(0);
var z = x.insertCell(1);
var a = x.insertCell(2);
y.innerHTML = first;
z.innerHTML = second;
a.innerHTML = '<input type="button" onclick="deleteRow(this)" value="Delete">';
}
function populateheader() {
// Populate the top fields with the name and how many it serves
var arrayraw = localStorage.getItem("Recipe")
var notraw = JSON.parse(arrayraw)
var element = document.getElementById("header");
element.innerHTML = notraw[0][0];
var header2 = document.getElementById("details");
// if statement ensures the header doesn't say '0' people, instead says no people
if (notraw[0][1] === 0) {
header2.innerHTML = "Serves no people"
} else {
header2.innerHTML = "Serves " + notraw[0][1] + " people"
}
console.log("Now populating Header, The Title was: " + notraw[0][0] + " And it served: " + notraw[0][1]);
}
function populatetable() {
console.log("Now populating the table")
// Here we're gonna populate the table with data that was in the loaded array.
var arrayraw = localStorage.getItem("Recipe")
var notraw = JSON.parse(arrayraw)
if (notraw.length === 0 || notraw.length === 1) {
console.log("Array was empty.")
} else {
var count = 1;
while (count < notraw.length) {
amount = notraw[count][1] + " " + notraw[count][2]
insRow(notraw[count][0], amount)
console.log("Inserted Ingredient: " + notraw[count][0] + notraw[count][1])
count++;
}
}
}
EDIT
The lesson, learned with the help of #Alex, is that you should never put function declarations in block scope. Not that I intended to do this, but if you slip up, it can cause big problems.
I have a script file that seems to be getting compressed via Google Closure incorrectly. When I run my app with the original code, all works fine. But when I try to compress it with Google Closure, some errors get introduced.
I am NOT using the advanced option; I'm using the basic, default mode
Obviously I can't expect anyone to debug the compressed file, but I'm hoping someone can look at the uncompressed code and let me know if I'm somehow doing something insanely stupid that would trick Closure.
Some notes on the minified code:
Closure is inlining BEFramework.prototype.hstreamLoad and BEFramework.prototype.hstreamEvalJson, and seems to be utterly removing the helper functions getDeleteValue, getValueToDisplay, getDisplayForLabel and likely others.
Uncompressed file is below.
This code can manually be compiled by closure here, which should reproduce the symptoms described above.
(function() {
var $ = jQuery;
// Load and display the messages ("healthstream") for a given module.
// This requires that the module's HTML have specific features, see
// dashboard.htm and contactsManager/details/default.htm for examples.
// This also requires that the `request` support `pageIndex` and `pageSize`,
// so we can handle paging.
//
// Args: `options` An options object with these keys:
// `channelId` The channel ID of the module (for transmitRequest)
// `translationId` Optional alternate ID for translation (if not given,
// `channelId` is used).
// `action` The action (for transmitRequest)
// - Must support `pageIndex` and `pageSize`
// `request` The request (for transmitRequest)
// - Must include `pageIndex` and `pageSize`
// `complete` Optional callback triggered when the load is complete.
// `showOptions` Optional callback if an options menu is supported
// by the calling module. Receives a raw event instance
// and the item on which the options were triggered:
// function showOptions(event, item)
// `context` Optional context (`this` value) for the call to
// `complete` and/or `showOptions`
BEFramework.prototype.hstreamLoad = hstreamLoad;
function hstreamLoad(options) {
var inst = this;
var channelId, translationId, action, request, complete, showOptions, context,
pageIndex, pageCount, pageSize, pageCount,
btnPrevious, btnNext,
dataShownFlags;
// Get our arguments (with defaults)
channelId = options.channelId;
translationId = options.translationId || options.channelId;
action = options.action;
request = $.extend({}, options.request); // Create a *copy*, because we modify it when doing paging
complete = options.complete;
if (typeof complete !== "function") {
complete = undefined;
}
showOptions = options.showOptions;
if (typeof showOptions !== "function") {
showOptions = undefined;
}
context = options.context; // (undefined will automatically become the global object)
// Grab the initial pageIndex and pageSize
pageIndex = request.pageIndex || 1;
pageSize = request.pageSize || 100;
// Disable the button and show "searching" label
$('#healthStreamSearchButton')
.button("disable")
.button("option", "label", BETranslate(translationId, 'HealthStreamSearching'));
// Hook up the buttons; be a bit paranoid that they've been hooked before and clear previous handlers
btnPrevious = $('#healthStreamPagePrevious');
btnNext = $('#healthStreamPageNext');
btnPrevious.hide().unbind("click.paging").bind("click.paging", goToPreviousPage);
btnNext.hide().unbind("click.paging").bind("click.paging", goToNextPage);
// Do it
doLoad();
// === Support functions
// Trigger a load request
function doLoad() {
request.pageIndex = pageIndex;
request.pageSize = pageSize;
inst._transport.transmitRequest(channelId, action, request, hstreamLoaded);
}
// Hndle the load response
function hstreamLoaded(objResponse) {
var healthStream = objResponse.items;
var total = objResponse.total;
var tbody = $('#healthStreamList');
// Need to make this update optional
$('#pageHeaderName').html(BETranslate(translationId, 'HeaderActivity') + ' (' + String(total) + ')');
$('#healthStreamSearchButton')
.button("enable")
.button("option", "label", BETranslate(translationId, 'HealthStreamSearch'));
tbody.empty();
btnPrevious.hide();
btnNext.hide();
if (healthStream.length > 0) {
pageCount = Math.ceil(total / pageSize);
if (pageCount > 1) {
if (pageIndex > 1) {
btnPrevious.show();
}
if (pageIndex < pageCount) {
btnNext.show();
}
}
var item;
var tr;
var tdMain;
var daysHash = {};
var creationDate;
var key;
var today = new Date();
var yesterday = new Date();
var msg;
yesterday.setDate(yesterday.getDate() - 1);
dataShownFlags = {};
for (var x = 0; x < healthStream.length; x++) {
item = healthStream[x];
msg = inst.hstreamEvalJson(item);
if (msg.length > 0) {
creationDate = new Date(item.CreationDate);
key = [creationDate.getYear(), creationDate.getMonth(), creationDate.getDate()].join('-');
if (!daysHash[key]) {
if (isDateEqual(creationDate, today)) {
addRowHeader(tbody, BETranslate(inst._channelId, 'HSToday'));
}
else if (isDateEqual(creationDate, yesterday)) {
addRowHeader(tbody, BETranslate(inst._channelId, 'HSYesterday'));
}
else {
addRowHeader(tbody, creationDate.toString('MM/dd/yyyy'));
}
daysHash[key] = true;
}
tr = $(
"<tr>" +
"<td class='date' style='white-space:nowrap;'>" + new Date(item.CreationDate).toString('h:mm tt') + "</td>" +
"<td class='main'><span class='name'>" + msg + "</span>" +
"</tr>"
);
tbody.append(tr);
if (showOptions) {
tr.find("td.main").prepend($("<em rel='opt'> </em>").click(makeShowOptionsHandler(item)));
}
}
}
// If any of the templates created links with a `data` attribute, hook them up
$('#healthStreamList a[data]').click(showTitle).each(function (index) {
this.id = 'data' + index;
});
}
else {
tbody.html('<tr><td colspan="2">' + BETranslate(inst._channelId, 'HSNoActivity') + '</td></tr>');
}
// Trigger completion callback
if (complete) {
complete.call(context, objResponse);
}
}
function makeShowOptionsHandler(item) {
// Our event comes to us from jQuery, but we pass on the raw
// event to the callback
return function (event) {
showOptions.call(context, event.originalEvent || event, item);
};
}
function addRowHeader(listRef, name) {
listRef.append(
"<tr>" +
"<td colspan='2' class='divider'>" + name + "</td>" +
"</tr>"
);
}
function showTitle(event) {
$.stopEvent(event);
var link = this;
var $link = $(this);
var href = $link.attr("href"); // We want the attribute, not the property (the property is usually expanded)
var hrefTitle = $link.attr('hreftitle') || BETranslate(inst._channelId, 'HSMoreInfo');
var data = $link.attr('data') || "";
var linkId = link.id;
if (!dataShownFlags[linkId]) {
dataShownFlags[linkId] = true;
if (data) {
var div = $(
"<div class='data'>" +
"<span data-linkId='" + linkId + "' class='close'>x</span>" +
"<table><thead></thead></table>" +
"</div>"
);
$link.parent().append(div);
var thead = div.find("thead");
var arr = data.split('~');
var splitEntry;
for (var x = 0; x < arr.length; x++) {
splitEntry = arr[x].split('|');
if (splitEntry[0] === 'Changed length') {
splitEntry[1] = splitEntry[1].replace(/\d+/g, BEFramework.prettyTime);
}
if (splitEntry.length > 1 && splitEntry[1].length > 0) {
thead.append(
"<tr>" +
"<td class='hslabel'>" + splitEntry[0] + ":</td>" +
"<td>" + splitEntry[1] + "</td>" +
"</tr>"
);
}
}
div.find("span:first").click(hideTitle);
if (href && href !== "#") {
$("<a target='_blank'>" + hrefTitle + "</a>").attr("href", href).appendTo(div);
}
}
}
}
function hideTitle(event) {
var $this = $(this),
linkId = $this.attr("data-linkId");
delete dataShownFlags[linkId];
$this.parent().remove();
return false;
}
function goToPreviousPage(event) {
--pageIndex;
doLoad();
return false;
}
function goToNextPage(event) {
++pageIndex;
doLoad();
return false;
}
}
var ___x = false;
var __i = 0;
BEFramework.prototype.hstreamEvalJson = hstreamEvalJson;
function hstreamEvalJson(item) {
var inst = this;
if (item.Action === 'saveinsurance' && !___x && __i != 0){
var start = +new Date();
__i = 1;
}
var userId = inst._BEUser ? inst._BEUser.getId() : -1;
var json = eval('(' + item.JSON + ')');
var key = 'HS' + item.Module + '_' + item.Action;
var msg = BETranslate(inst._channelId, key);
var fromIsMe = item.CreatedByContactId == userId;
var toIsMe = item.ContactId == userId;
var fromString = (fromIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYou') + '</strong>' : '<a class="vcard" contactId="' + item.CreatedByContactId + '">' + item.CreatedByName + '</a>';
var toString = (toIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYour') + '</strong>' : '<a class="vcard" contactId="' + item.ContactId + '">' + item.ContactName + '</a>';
var fromString2 = (fromIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYour').toLowerCase() + '</strong>' : '<a class="vcard" contactId="' + item.CreatedByContactId + '">' + item.CreatedByName + '</a>';
var toString2 = (toIsMe) ? '<strong>' + BETranslate(inst._channelId, 'HSYou').toLowerCase() + '</strong>' : '<a class="vcard" contactId="' + item.ContactId + '">' + item.ContactName + '</a>';
var subFormat, subProps;
var configObject = (BEFramework.healthStreamConfig[item.Module] && BEFramework.healthStreamConfig[item.Module][item.Action]) || {};
var standardCase = configObject.standardCase;
var suppress = configObject.suppress || [];
var propertiesInOrder = configObject.displayOrder || [];
if (msg.indexOf('not found in module') != -1) {
try {
switch (item.Module) {
case 'contacts':
if (item.Action == 'setpermission' || item.Action == 'deleterelationship' || item.Action == 'addinvite') {
msg = BETranslate(inst._channelId, key + json.type.toString());
}
break;
case 'tasks':
if (item.Action == 'savetask') {
msg = BETranslate(inst._channelId, key + json.type.toString());
}
break;
default:
msg = '';
}
} catch (ex) {
msg = '';
}
}
for (var prop in json) {
if (typeof (json[prop]) == 'object') {
if (prop === 'changes' || prop === 'deleted'){
subProps = json[prop];
for (var propName in subProps) {
if (indexInArrayCI(propName, propertiesInOrder) === -1 && indexInArrayCI(propName, suppress) === -1){
propertiesInOrder.push(propName);
}
}
}
if (prop == 'changes') {
var changes = '';
var changeFrom = BETranslate(inst._channelId, 'HSChangedFrom');
var changeTo = BETranslate(inst._channelId, 'HSChangedTo');
for (var i = 0; i < propertiesInOrder.length; i++) {
var subprop = propertiesInOrder[i];
if (getObjectValCI(subProps, subprop) == null) continue;
var subSplit = stripHtml(getObjectValCI(subProps, subprop)).split('|');
if (subSplit.length === 1) {
subFormat = BETranslate(inst._channelId, 'HS' + item.Module + '_changes_' + subprop);
if (subFormat.indexOf('not found in module') < 0) {
changes += $.sandr(subFormat, '#{value}', subSplit[0]);
}
else {
changes += "*|" + subprop + " " + subSplit[0] + "~";
}
}
else {
var fromValue = stripHtml(subSplit[0]);
var toValue = stripHtml(subSplit[1]);
var packetInfo = processChangedValues(subprop, fromValue, toValue);
if (packetInfo.skip) continue;
changes = changes + changeFrom + packetInfo.display + '|' + packetInfo.fromValue + '<b>' + changeTo + '</b>' + packetInfo.toValue + '~';
}
}
msg = $.sandr(msg, '#{' + prop + '}', changes);
} else if (prop == 'deleted') {
var deleted = '';
for (var i = 0; i < propertiesInOrder.length; i++) {
var subprop = propertiesInOrder[i];
var currentValue = getObjectValCI(subProps, subprop);
if (currentValue == null || currentValue.toString().length === 0) continue;
deleted = deleted + getDisplayForLabel(subprop) + '|' + getDeleteValue(subprop, currentValue) + '~';
}
msg = $.sandr(msg, '#{' + prop + '}', deleted);
}
} else {
msg = $.sandr(msg, '#{' + prop + '}', $.sandr(json[prop], '"', ' '));
}
function processChangedValues(label, fromValue, toValue){
var typeFormat = (getObjectValCI(configObject, label) || {}).type;
var result = {};
if (typeFormat === 'date'){
var d1 = new Date(fromValue);
var d2 = new Date(toValue);
if (isDateEqual(d1, d2)) result.skip = true;
}
result.fromValue = getValueToDisplay(fromValue, typeFormat);
result.toValue = getValueToDisplay(toValue, typeFormat);
result.display = getDisplayForLabel(label)
return result;
}
function getDeleteValue(label, value){
var typeFormat = (getObjectValCI(configObject, label) || {}).type;
return getValueToDisplay(value, typeFormat);
}
function getValueToDisplay(rawValue, typeFormat){
if (typeFormat === 'date'){
var d = new Date(rawValue);
return isNaN(d.getTime()) ? rawValue : d.toString('MM/dd/yyyy');
} else if (typeof typeFormat === 'function') {
return typeFormat(rawValue)
} else {
return rawValue;
}
}
function getDisplayForLabel(label){
var fixCaseOfProperty = standardCase === '*' || indexInArrayCI(label, standardCase) > -1;
var rawConfigForLabel = getObjectValCI(configObject, label) || {};
return (rawConfigForLabel && rawConfigForLabel.display)
|| (fixCaseOfProperty ? fixCase(label) : null)
|| label;
}
}
msg = $.sandr(msg, '#{contactId}', item.ContactId);
msg = $.sandr(msg, '#{from}', fromString);
msg = $.sandr(msg, '#{to}', toString);
msg = $.sandr(msg, '#{from2}', fromString2);
msg = $.sandr(msg, '#{to2}', toString2);
msg = $.sandr(msg, '#{recordId}', item.RecordId);
msg = msg.replace(/#{[\S]*}/g, '');
if (item.Action === 'saveinsurance' && !___x && __i == 1){
var end = +new Date();
___x = true;
//alert(end - start);
}
if (item.Action === 'saveinsurance') __i++;
if (msg.indexOf('not found in module') == -1) {
return msg;
} else {
return '';
}
}
function stripHtml(html) {
var tmp = document.createElement('DIV');
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText;
}
function isDateEqual(date1, date2) {
if (date1.getDate() === date2.getDate() &&
date1.getMonth() === date2.getMonth() &&
date1.getYear() === date2.getYear()) {
return true;
}
else {
return false;
}
}
function getObjectValCI(obj, key){
for (var k in obj){
if (k.toLowerCase() === key.toLowerCase()){
return obj[k];
}
}
}
function indexInArrayCI(item, arr){
if (!$.isArray(arr)) arr = [];
var target = item.toString().toLowerCase();
for (var i = 0; i < arr.length; i++){
if (target === arr[i].toLowerCase()) return i;
}
return -1;
}
function fixCase(str){
return str.replace(/[a-z][A-Z]/g, function(match) { return match.charAt(0) + ' ' + match.charAt(1).toLowerCase(); }).toLowerCase()
.replace(/\sid\s/g, ' ID ')
.replace(/\sid$/g, ' ID')
.replace(/^id$/g, 'ID');
}
})();
When you use closure compiler you're giving up some control over your code. It will do all sorts of tricks, and potentially remove unused code.
It appears as though your functions are not removed, but are renamed.
For example, your call to getDeleteValue...
getDeleteValue(subprop, currentValue)
is now...
l(g,r)
Because getDeleteValue was not exported, Closure renamed it.
Working with Closure Compiler takes a bit of finesse and quite a bit of documentation scouring until you're familiar with how it works.
Well, there are too many errors to think of. First of all, I don't understand if you want static reference or instantiated values. You are not using jsDoc tags or anything like that. The Compiler does it's best work only with the corresponding jsDoc tag. You're logic is very weird and ill formulated. Prototype alternations, etc, all happening in an IIFE(immediately invoked function expression). Are your functions static? Are they constructors? Are we human or are we dancer?
an IIFE executes before the DOMContentLoaded event is fired by the browser. The most you can do is a jQuery IIFE equivalent $(function() {})(); which binds that to the DOMReady or DOMContentLoaded callback. You are defining inline functions inside blocks, which is not even in the ECMA Language.
While most script engines support Function Declarations within blocks it is not part of ECMAScript (see ECMA-262, clause 13 and 14). Worse implementations are inconsistent with each other and with future EcmaScript proposals. ECMAScript only allows for Function Declarations in the root statement list of a script or function. Instead use a variable initialized with a Function Expression to define a function within a block.
var myFunctionName = function (params) {};
You are also missing loads of semi-colons. Automatic semi-colon insertion on interpretation of your JS is not exactly flawless, so make a habit out of it.
Relying on implicit insertion can cause subtle, hard to debug problems. Don't do it. You're better than that.
There are a couple places where missing semicolons are particularly dangerous:
// 1.
MyClass.prototype.myMethod = function() {
return 42;
} // No semicolon here.
(function() {
// Some initialization code wrapped in a function to create a scope for locals.
})();
var x = {
'i': 1,
'j': 2
} // No semicolon here.
// 2. Trying to do one thing on Internet Explorer and another on Firefox.
// I know you'd never write code like this, but throw me a bone.
[normalVersion, ffVersion][isFF]();
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here.
// 3. conditional execution a la bash
-1 == resultOfOperation() || die();
So what happens?
JavaScript error - first the function returning 42 is called with the second function as a parameter, then the number 42 is "called" resulting in an error.
You will most likely get a 'no such property in undefined' error at runtime as it tries to call x[ffVersion][isIE]().
die is called unless resultOfOperation() is NaN and THINGS_TO_EAT gets assigned the result of die().
Why?
JavaScript requires statements to end with a semicolon, except when it thinks it can safely infer their existence. In each of these examples, a function declaration or object or array literal is used inside a statement. The closing brackets are not enough to signal the end of the statement. Javascript never ends a statement if the next token is an infix or bracket operator.
This has really surprised people, so make sure your assignments end with semicolons.
I need to keep track of a list of 25 favourites which are added from a list of ~100 entries. These favourites would be in a list of say
"favourite 1"
"favourite 2"
"favourite 3"
.... so on
and I need them to be stored persistently. I also would require them to be able to be deleted and replaced with another favourite string value. I have looked at How do I store an array in localStorage? but that doesn't work for me because when I declare var names=[]; on the javascript file, every time my favourites function runs it redeclares the array and clears everything.
I have tried doing something like:
function addToFav(string)
{
if (localStorage.fav1) // if fav1 was created before
{
alert("local storage fav1 present, don't make variables");
}
else
{
alert('fav variables never made, make them now');
var i=1;
for (i=1; i<=25; i++)
{
var favNumber = "fav" + i;
localStorage.favNumber = "x";
}
alert(localStorage.fav1); // outputs "undefined"
}
}
it was my intention to make localStorage variables of fav1,fav2,fav3...fav25 and then manage them individually. But this doesn't work since calling localStorage.favNumber = "x"; makes the local storage variable favNumberequal to "x" and not fav+i.
I'm out of ideas at the moment; I have looked at http://playground.html5rocks.com/#async_transactions to try using a HTML5 database but I'm very new to web development and it seems a bit too much for what I'm trying to do. Does anyone know how to fix this issue? Any information would be helpful.
This might help. I use these to store info in Local Storage with cookie backup...
function setLocalStorage(c_name, value) {
var exdays = 30;
// if localStorage is present, use that
if (('localStorage' in window) && window.localStorage !== null) {
localStorage[c_name] = value;
} else {
var exdate = new Date();
exdate.setDate(exdate.getDate() + exdays);
var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = c_name + "=" + c_value;
}
}
function getLocalStorage(c_name) {
// if localStorage is present, use that
if (('localStorage' in window) && window.localStorage !== null) {
return localStorage[c_name];
} else {
var i, x, y, ARRcookies = document.cookie.split(";");
for (i = 0; i < ARRcookies.length; i++) {
x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
x = x.replace(/^\s+|\s+$/g, "");
if (x == c_name) {
return unescape(y);
}
}
}
}