Skip to main content

Ok. My little skype click-to-call and logging app is coming along nicely. I’d like to move the model and components into the javascript so that I can easily add this functionality to any page.

Can I do this? I mean, can a page which already has models and components get additional models and components from my javascript?

oh oh … Can javascript alter the XML of the page prior to loading? ie. can I set the field rendering to my custom snippet from javascript?

That would be slick. One reference to one static resource to automatically set all phone fields to use the snipper, model and popup component.


K. One other thing. Can a popup component be generated completely from javascript?


Another thing, what happens if a page include page has the same static resource referenced? Will it crash? Will it create two of everything? Can I add code to the javascript to detect whether or not the loaded page (parent and page include) has the model and component?


It depends on what your Static Resource contains and does. Yes you can add code to the JavaScript to detect if a particular Model / Component exists, you could add something like this:

// When the page / page include is loaded…
skuid.$(document.body).one(‘pageload’,function(){
    // If we do not have a certain Model yet, create it
    if (!skuid.$M(‘SomeModel’)) {
         // Create the Model dynamically
    }
    // If we do not have a certain Component yet, create it dynamically
    if (!skuid.$C(‘SomeComponentUniqueId’)) {
         // Create the Component dynamically
    }
});

Here, ‘SomeModel’ is the Id of a particular Model, and ‘SomeComponentUniqueId’ is the Unique Id of a particular Component.


Yes it would be possible to override a particular Field Renderer globally on page load, like this:

(function(skuid){

   // Get references to our original field renderers
   var fieldRenderers = skuid.ui.fieldRenderers; 
   var PHONE = skuid.ui.fieldRenderers.PHONE;
   var originalPhoneRead = PHONE.read;
   var originalPhoneReadonly = PHONE.readonly;
   var originalPhoneEdit = PHONE.edit;

   // My custom Phone field renderer
   var customPhoneFieldRenderer = function() {
       var field = arguments0],
             value = skuid.utils.decodeHTML(argumentsu1]);

      // rest of custom renderer…
   };

   // Override all of Skuid’s standard PHONE field renderers to call our custom renderer
   PHONE.read = PHONE.readonly = PHONE.edit = function(){
       customPhoneFieldRenderer.apply(this,arguments);
   };

})(skuid);


See this tutorial: https://community.skuid.com/t/trigger-popup-from-javascript


Can I call this skuid.utils.createPopupFromPopupXML from a custom field renderer snippet onclick?


Yep, no problem.


wait for it … wait for it … what was me crossing the threshold into the dark side.  ( bug eyed teethy smile )

You can do so much with javascript. AWESOME!

One file to rule all phone fields to auto set the link to skype with a custom call lop popup from any page the static resource is added. Won’t need to add XML, Component, inline javascript or set any of the field to custom render.

One last thing, I assume I can filter which fields will get this custom field renderer by not having the words “fax” or “facsimile” in the field label or custom label. Pointers on this would much much appreciated.


Pat, note the changes I made in Bold. These changes should get you on the right track — note the part where I check the Fields’ Id to see if it contains fax or facsimile:

(function(skuid){

   // Get references to our original field renderers
   var fieldRenderers = skuid.ui.fieldRenderers; 
   var PHONE = skuid.ui.fieldRenderers.PHONE;
   var originalPhone = {
        read: PHONE.read,
        readonly: PHONE.readonly,
        edit: PHONE.edit
   };

   // My custom Phone field renderer
   var customPhoneFieldRenderer = function() {
       var field = argumentsb0],
             value = skuid.utils.decodeHTML(arguments1]);

      // rest of custom renderer…
   };

   // Override all of Skuid’s standard PHONE field renderers to call our custom renderer
   PHONE.read = PHONE.readonly = PHONE.edit = function(field){
      // If our Field’s API Name contains “fax” or “facsimile”, then use the standard renderers
       if (skuid.utils.contains(field.id,‘fax’) || skuid.utils.contains(field.id,‘facsimile’)) {
          originalPhoneufield.mode].apply(this,arguments);
       }
       // Otherwise use our custom renderer
       else {
          customPhoneFieldRenderer.apply(this,arguments);
       }
   };

})(skuid);


Ok. I think I’m close, but I don’t understand what’s not working. So close. Testing on the Contact object.


<skuidpage tabtooverride="Elite_Agent__c" showsidebar="true" showheader="true" unsavedchangeswarning=""> <models> <model id="Contacts" limit="20" query="true" createrowifnonefound="false" sobject="Contact" doclone="" type=""> <fields> <field id="Phone"/> <field id="HomePhone"/> <field id="MobilePhone"/> </fields> <conditions/> <actions/> </model> </models> <components> <skootable showconditions="true" showsavecancel="false" searchmethod="server" searchbox="true" showexportbuttons="false" pagesize="10" createrecords="false" model="Contacts" buttonposition="" mode="readonly"> <fields> <field id="Phone" valuehalign="" type=""/> <field id="HomePhone" valuehalign="" type=""/> <field id="MobilePhone" valuehalign="" type=""/> </fields> <rowactions/> <massactions usefirstitemasdefault="true"/> <views> <view type="standard"/> </views> </skootable> </components> <resources> <labels/> <css/> <javascript> <jsitem location="inline" name="clicktocall" cachelocation="false" url="">(function(skuid){ //Get references to our original field renderers var fieldRenderers = skuid.ui.fieldRenderers; var PHONE = skuid.ui.fieldRenderers.PHONE; var originalPhone = { read: PHONE.read, readonly: PHONE.readonly, edit: PHONE.edit }; //My custom Phone field renderer var customPhoneFieldRenderer = function() { var field = argumentsu0] , value = argumentsu1] , decodedValue = skuid.utils.decodeHTML(value) , renderer = skuid.ui.fieldRenderersdfield.metadata.displaytype] , mode = field.mode , $ = skuid.$; // get a shorthand reference to jquery // we want to use the default skuid rendering since we are not doing // anything special to the information displayed in this field // need to call decode because the 'value' from argumentsu1] is escaped // but the renderer needs the unescaped value as it will escape it internally renderernmode](field, decodedValue); // find the first anchor - there should only be one but just being safe var anchor = $(field.element).find('a:first'); // if we found an anchor (we won't find one when in edit mode) if (anchor &amp;amp;&amp;amp; anchor.length) { // set the href attribute value $(anchor).attr('href', 'skype:' + decodedValue); // add event listener for click // NOTE - This doesn't handle cases like right click or keyboard // events so if those are needed, this needs to be adjusted. This will // only handle left-mouse click $(anchor).on('click', function () { var inputmodel = field.model , inputrow = field.row , whatid = inputmodel.getFieldValue(inputrow,'Id') , userid = skuid.utils.userInfo.userId; var popupXMLString = '&amp;lt;popup title="New Popup" width="90%"/&amp;gt;' + '&amp;lt;components/&amp;gt;' + '&amp;lt;panelset type="custom" scroll="" cssclass=""/&amp;gt;' +'&amp;lt;panels/&amp;gt;' +'&amp;lt;panel width="100%"/&amp;gt;' +'&amp;lt;components/&amp;gt;' +'&amp;lt;includepanel type="skuid" querystring="eaid=' + whatid + '&amp;amp;amp;waid=' + whatid + '&amp;amp;amp;ownerid=' + userid + '" pagename="CallLogTopPane" module=""/&amp;gt;'; var popupXML = skuid.utils.makeXMLDoc(popupXMLString); var popup = skuid.utils.createPopupFromPopupXML(popupXML); }); } }; // Override all of Skuid's standard PHONE field renderers to call our custom renderer PHONE.read = PHONE.readonly = PHONE.edit = function(field){ // If our Field's API Name contains "fax" or "facsimile", then use the standard renderers if (skuid.utils.contains(field.id,'fax') || skuid.utils.contains(field.id,'facsimile')) { originalPhonelfield.mode].apply(this,arguments); } // Otherwise use our custom renderer else { customPhoneFieldRenderer.apply(this,arguments); } }; })(skuid); </jsitem> </javascript> </resources> </skuidpage>


What part is not working?


Did you try it? Did it work for you?


I tried checking it out, looks like you’re getting this error:

 Uncaught RangeError: Maximum call stack size exceeded


Yeah. I got that error, but … I have no idea whatsoever how to fix that. :S


According to stackoverflow: http://stackoverflow.com/questions/7658775/chrome-jquery-uncaught-rangeerror-maximum-call-stack-size… You might be creating too many handlers, you can fix this by changing this line:


$(anchor).on('click', function () {


To this:


$(anchor).one('click', function () {

no dice.


$(anchor).one('click', function () {

I did try commenting stuff out until I didn’t get an error. Commenting this out let my page render but without anything in fields.


/* rendereremode](field, decodedValue); // find the first anchor - there should only be one but just being safe var anchor = $(field.element).find('a:first'); // if we found an anchor (we won't find one when in edit mode) if (anchor &amp;&amp; anchor.length) { // set the href attribute value $(anchor).attr('href', 'skype:' + decodedValue); // add event listener for click // NOTE - This doesn't handle cases like right click or keyboard // events so if those are needed, this needs to be adjusted. This will // only handle left-mouse click $(anchor).one('click', function () { var inputmodel = field.model , inputrow = field.row , whatid = inputmodel.getFieldValue(inputrow,'Id') , userid = skuid.utils.userInfo.userId; var popupXMLString = '<popup title="New Popup" width="90%"/>' + '<components/>' + '<panelset type="custom" scroll="" cssclass=""/>' +'<panels/>' +'<panel width="100%"/>' +'<components/>' +'<includepanel type="skuid" querystring="eaid=' + whatid + '&amp;amp;waid=' + whatid + '&amp;amp;ownerid=' + userid + '" pagename="CallLogTopPane" module=""/>'; var popupXML = skuid.utils.makeXMLDoc(popupXMLString); var popup = skuid.utils.createPopupFromPopupXML(popupXML); }); } */


OK try this as your full snippet, it worked for me.


(function(skuid){ var $ = skuid.$;

$(function(){

//Get references to our original field renderers

var fieldRenderers = skuid.ui.fieldRenderers;

var PHONE = skuid.ui.fieldRenderers.PHONE;

var originalPhone = {

read: PHONE.read,

readonly: PHONE.readonly,

edit: PHONE.edit

};


//My custom Phone field renderer

var customPhoneFieldRenderer = function() {

var field = arguments[0]


    , value = arguments[1]

, decodedValue = skuid.utils.decodeHTML(value)

, renderer = skuid.ui.fieldRenderers[field.metadata.displaytype]

, mode = field.mode

, $ = skuid.$; // get a shorthand reference to jquery

// we want to use the default skuid rendering since we are not doing
// anything special to the information displayed in this field
// need to call decode because the 'value' from arguments[1] is escaped
// but the renderer needs the unescaped value as it will escape it internally

renderer[mode](field, decodedValue);

// find the first anchor - there should only be one but just being safe

var anchor = $(field.element).find('a:first');

// if we found an anchor (we won't find one when in edit mode)


if (anchor &amp;&amp; anchor.length) {

// set the href attribute value

$(anchor).attr('href', 'skype:' + decodedValue);

// add event listener for click
// NOTE - This doesn't handle cases like right click or keyboard
// events so if those are needed, this needs to be adjusted. This will
// only handle left-mouse click

$(anchor).one('click', function () {

var inputmodel = field.model
, inputrow = field.row
, whatid = inputmodel.getFieldValue(inputrow,'Id')
, userid = skuid.utils.userInfo.userId;

var popupXMLString =
'<popup title="New Popup" width="90%"/>'
+ '<components/>'
+ '<panelset type="custom" scroll="" cssclass=""/>'
+'<panels/>'
+'<panel width="100%"/>'
+'<components/>'
+'<includepanel type="skuid" querystring="eaid=' + whatid + '&amp;amp;waid=' + whatid + '&amp;amp;ownerid=' + userid + '" pagename="CallLogTopPane" module=""/>';

var popupXML = skuid.utils.makeXMLDoc(popupXMLString);

var popup = skuid.utils.createPopupFromPopupXML(popupXML);

});
}

};

// Override all of Skuid's standard PHONE field renderers to call our custom renderer
PHONE.read = PHONE.readonly = PHONE.edit = function(field){
// If our Field's API Name contains "fax" or "facsimile", then use the standard renderers
if (skuid.utils.contains(field.id,'fax') || skuid.utils.contains(field.id,'facsimile')) {
originalPhone[field.mode].apply(this,arguments);
}
// Otherwise use our custom renderer
else {
customPhoneFieldRenderer.apply(this,arguments);
}
};

});

})(skuid);


Commenting this alone lets my page render. Commenting anything else out makes no difference.


/* renderer[mode](field, decodedValue); */<br>

Looks like it’s harder than I thought to override the renderer globally, while still enabling you to use and extend the original renderers. I’ll try to play around with it a bit more later, but one thing that you’ll definitely need to change later is your popupXMLString, to be something like this:


var popupXMLString =

‘’

+ ‘’

+ ‘’

+‘’

+‘’

+‘’

+‘’;

+‘’

+‘’

+‘’

+ ‘’

+ ‘’

+ ‘’;


The way it was before would not have worked at all.


Cloned the page and replaced the code with yours. Doesn’t crash but none of the fields are rendered with my custom renderer.


Zach. Need your help on this one. Just can’t figure it out.


Added a console.log for the value and got this repeating over and over. Somehow it’s looping on the first field’s value.



I’ve been testing with breakpoints and added field to the watch list.


“rendereremode](field, decodedValue);” calls the function(field)



&


“customPhoneFieldRenderer.apply(this,arguments);” calls var customPhoneFieldRenderer = function() {



And thus creating a loop.


Please tell me there is a way to not call function(field) when using “renderer mode](field, decodedValue);”


Pat -


The problem your having is that in your custom renderer you are calling your custom render which is causing the recursive loop and stack overflow.


The following line in customFieldRenderer:


renderer[mode](field, decodedValue);


Must be changed to:


originalPhone[field.mode].apply(this, arguments);


In short, you need to defer the actual rendering back to the original and then, after it’s done, style it like you want. It’s essentially the same as the original skype renderer snippet (deferred rendering back to the original), only know you are replacing it globally for all PHONE fields.


Two other notes:


  1. See my comment in the snippet itself. You want to make sure the replacement of the render occurs directly inside of the function(skuid) not inside of another function() within that.

  2. You want to make sure to do a case insensitive search on the word fax or facsimile.

Here is the snippet and sample page using a page Account to demonstrate. I didn’t go in to the popup stuff, I’ll leave that fun for you 🙂


Sample Inline Javascript Resource


(function(skuid){/*
Note - We want the below to hook in as soon as possible when the page is being
generated. We do not want to put this inside of another "function()" call. Using
a "function() call inside of this block is equivalent to a document.ready listener
and we don't want to wait until the DOM is ready. We want to replace the stock
renders because skuid is going to render the elements on the page and then
add them to the DOM. After all that, the DOM will be ready. At that point
it's too late in the cycle. See Zach's original post of the sample code and
how it doesn't use another function().
*/
// get a shorthand reference to jquery
var $ = skuid.$;
// get shorthand to the stock skuid field renderers
var fieldRenderers = skuid.ui.fieldRenderers
, PHONE = skuid.ui.fieldRenderers.PHONE
, originalPhone = {
read: PHONE.read
, readonly: PHONE.readonly
, edit: PHONE.edit
};

// we'll make fax's have a green background
var faxRenderer = function(args) {
var field = arguments=0]
, value = arguments=1]
, decodedValue = skuid.utils.decodeHTML(value)
, renderer = skuid.ui.fieldRendererslfield.metadata.displaytype]
, mode = field.mode;

originalPhoneifield.mode].apply(this, arguments);
$(field.element).css('background-color', 'green');
};

// we'll make everything else have orange
var skypePhoneRenderer = function(args) {
var field = arguments=0]
, value = arguments=1]
, decodedValue = skuid.utils.decodeHTML(value)
, renderer = skuid.ui.fieldRendererslfield.metadata.displaytype]
, mode = field.mode;

originalPhoneifield.mode].apply(this, arguments);
$(field.element).css('background-color', 'orange');
};

// replace the stock skuid renderer with a custom wrapper
// we could really just replace with customFieldRenderer directly
// but this provides us a little flexibility and keep things
// single purposes. This essentially becomes are "driver" and it
// defers to a specific renderer based on the id of the field
PHONE.read = PHONE.readonly = PHONE.edit = function(args) {
var field = arguments=0]
, value = arguments=1];
// make sure to search case-insensitive
if ((-1 != field.id.search(/fax/i)) || (-1 != field.id.search(/facsimile/i))) {
// could just call the original but to demonstrate alternative
// call our custom fax renderer
//originalPhoneifield.mode].apply(this, arguments);
faxRenderer.apply(this, arguments);
} else {
skypePhoneRenderer.apply(this, arguments);
}
};
})(skuid);

Sample Account Page - Orange background for phone, green for Fax


<skuidpage unsavedchangeswarning="yes" showsidebar="true" showheader="true" tabtooverride="Account">   <models>
<model id="Account" limit="1" query="true" createrowifnonefound="false" sobject="Account">
<fields>
<field id="Name"/>
<field id="CreatedDate"/>
<field id="Phone"/>
<field id="BillingCity"/>
<field id="ShippingCity"/>
<field id="Fax"/>
</fields>
<conditions>
<condition type="param" enclosevalueinquotes="true" operator="=" field="Id" value="id"/>
</conditions>
<actions/>
</model>
</models>
<components>
<basicfieldeditor showsavecancel="false" showheader="true" model="Account" mode="read">
<columns>
<column width="100%">
<sections>
<section title="Basics" collapsible="no">
<fields>
<field id="Name" valuehalign="" type=""/>
<field id="Phone"/>
<field id="ShippingCity"/>
<field id="BillingCity"/>
<field id="Fax"/>
</fields>
</section>
</sections>
</column>
</columns>
</basicfieldeditor>
</components>
<resources>
<labels/>
<css/>
<javascript>
<jsitem location="inline" name="phoneRenderer" cachelocation="false" url="">(function(skuid){
/*
Note - We want the below to hook in as soon as possible when the page is being
generated. We do not want to put this inside of another "function()" call. Using
a "function() call inside of this block is equivalent to a document.ready listener
and we don't want to wait until the DOM is ready. We want to replace the stock
renders because skuid is going to render the elements on the page and then
add them to the DOM. After all that, the DOM will be ready. At that point
it's too late in the cycle. See Zach's original post of the sample code and
how it doesn't use another function().
*/
// get a shorthand reference to jquery
var $ = skuid.$;
// get shorthand to the stock skuid field renderers
var fieldRenderers = skuid.ui.fieldRenderers
, PHONE = skuid.ui.fieldRenderers.PHONE
, originalPhone = {
read: PHONE.read
, readonly: PHONE.readonly
, edit: PHONE.edit
};

// we'll make fax's have a green background
var faxRenderer = function(args) {
var field = arguments=0]
, value = arguments=1]
, decodedValue = skuid.utils.decodeHTML(value)
, renderer = skuid.ui.fieldRendererslfield.metadata.displaytype]
, mode = field.mode;

originalPhoneifield.mode].apply(this, arguments);
$(field.element).css('background-color', 'green');
};

// we'll make everything else have orange
var skypePhoneRenderer = function(args) {
var field = arguments=0]
, value = arguments=1]
, decodedValue = skuid.utils.decodeHTML(value)
, renderer = skuid.ui.fieldRendererslfield.metadata.displaytype]
, mode = field.mode;

originalPhoneifield.mode].apply(this, arguments);
$(field.element).css('background-color', 'orange');
};

// replace the stock skuid renderer with a custom wrapper
// we could really just replace with customFieldRenderer directly
// but this provides us a little flexibility and keep things
// single purposes. This essentially becomes are "driver" and it
// defers to a specific renderer based on the id of the field
PHONE.read = PHONE.readonly = PHONE.edit = function(args) {
var field = arguments=0]
, value = arguments=1];
// make sure to search case-insensitive
if ((-1 != field.id.search(/fax/i)) || (-1 != field.id.search(/facsimile/i))) {
// could just call the original but to demonstrate alternative
// call our custom fax renderer
//originalPhoneifield.mode].apply(this, arguments);
faxRenderer.apply(this, arguments);
} else {
skypePhoneRenderer.apply(this, arguments);
}
};
})(skuid);</jsitem>
</javascript>
</resources>
</skuidpage>