Calendar Expand All by default - calendar

Anyone know how to have the calendar view expanded all by default?? Seen a lot of information on how to do this with SP 2007 but not SP 2010.

Kuldeep, thanks for this script! I have modified it slightly to expand all rows of the calendar by removing the break and checking to see if the anchor text is "collapse". If it's not "collapse", a click is executed. Note that you only need to click on one item per row. Otherwise, you might toggle back to collapsed.
Another problematic side effect of adding the CEWP to the Calendar page is that you lose the Change View dropdown. You have to use the calendar ribbon to change views.
I would also like to find a more robust solution, but here is my slighly improved solution:
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push("myFuncAfterLoad");
function myFuncAfterLoad() {
var oldCalendarNotify4a = SP.UI.ApplicationPages.CalendarNotify.$4a;
SP.UI.ApplicationPages.CalendarNotify.$4a = function () {
oldCalendarNotify4a();
myFuncToExpandCalendar();
}
}
function myFuncToExpandCalendar() {
try {
var aTags = document.getElementsByTagName('A');
for (i = 0; i < aTags.length; i++) {
if ((aTags[i].evtid == "expand_collapse") && (aTags[i].innerText != "collapse")) {
(aTags[i]).click();
}
}
}
catch (err) {
alert(err.message);
}
}
</script>

Not the best approach but it works, add the following script on your page in CEWP or inline:
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push("myFuncafterLoad");
function myFuncafterLoad(){var OldCalendarNotify4a = SP.UI.ApplicationPages.CalendarNotify.$4a;SP.UI.ApplicationPages.CalendarNotify.$4a = function (){myFunctoExpandCalendar();OldCalendarNotify4a();}}
function myFunctoExpandCalendar(){try{var aTags=document.getElementsByTagName('A');for(i=0;i<aTags.length;i++){try{if(aTags[i].evtid=="expand_collapse"){aTags[i].click();break;}}catch(err){ alert('Bad Call at' + aTags[i].href);}}}catch(err){alert(err.message);}}</script>
What it does is after the calendar is loaded it searches for the first expand link and simulates a click.
I had to use the traditional Javascript as I cannot trigger click using JQuery, because the click method is not added using JQuery.
Let me know if somefine finds a better solution.

Two options for this one. Stick one or the other of the following in a CEWP.
The first one will override the function that is used to determine how many items to display before showing the expand/collapse link. I have set this to 100 in the example below to ensure that in my use case, I never even see the expand/collapse buttons and I always get every item rendered all the time.
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push('WaitForCalendarToLoad');
function WaitForCalendarToLoad()
{
SP.UI.ApplicationPages.SummaryItemRenderer.prototype.$2u = function ()
{
ULSvSp: ;
if (!this.$1A_1) this.$1A_1 = 100;
return this.$1A_1
}
}
</script>
The second option overrides the calendar notify event, so that after all the items are rendered, the script will find the first calendar instance, and call it's expand all function. This will ensure every cell is expanded by default but will still show the collapse links.
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push('WaitForCalendarToLoad');
function WaitForCalendarToLoad()
{
var old$4a = SP.UI.ApplicationPages.CalendarNotify.$4a;
SP.UI.ApplicationPages.CalendarNotify.$4a = function ()
{
old$4a();
ctrl = SP.UI.ApplicationPages.CalendarInstanceRepository.firstInstance();
if (ctrl) {
ctrl.expandAll();
}
}
}
</script>

I'm using this (in 2013):
<style>
.ms-cal-nav { display:none; }
</style>
<script type="text/javascript">
LoadSodByKey("SP.UI.ApplicationPages.Calendar.js", function () {
window.setTimeout(ExpandCalendar, 500);
});
function ExpandCalendar() {
// expand always
$('.ms-cal-nav:has(img[src$="/expandbttn.gif"])').each(function() { this.click(); });
window.setTimeout(ExpandCalendar, 1000);
}
</script>
It runs every second, which isn't great, but seems to do the job

So based on the original solutions provided by Andrew Peacock (presumably for SP2010), I was able to use those as a stating point to put together a working solution for SP2016.
The big difference seems to be that CalendarNotify is deprecated in SP2016, which causes many of the SP2010-based solutions not to work in newer versions. Then there are additional complications if using overlaid calendars; it seems that the HTML elements for events on overlaid calendars are not rendered until very late in the page load process - even waiting on the last sourced script on the page (sp.ui.socialribbon.js) using SP.SOD does not wait long enough for those to be available. I believe this is why most, if not all of the solutions for SP2016 calendars that rely on these being available must end up using either setTimeout() or setInterval(). In our case, for the second solution, we need to wait until all overlaid events have been rendered on the calendar before calling the calendar's expandAll() function or it won't work.
With that said, these are the two SP2016-compatible solutions I came up with, based off the original SP2010 solutions noted above. The first removes the expand/collapse link altogether and ensures that the calendar will always be expanded to show all events.
The first method overrides the built-in function that sets the "collapse item limit" (the number of items that will force the "expand/collapse" link to appear).
<script type="text/javascript">
function setCalendarCollapseItemLimit() {
// Configure the calendar's "collapse item limit" so that it always shows
// all items by default and never collapses them.
SP.UI.ApplicationPages.SummaryItemRenderer.prototype.$3V_1 = function() {
ULSvSp:;
if(!this.$1B_1) {
// This sets the "collapse item limit" to 100 events
this.$1B_1 = 100;
}
return this.$1B_1;
}
}
// Call our function once the necessary script is loaded (sp.ui.applicationpages.calendar.js in this case).
ExecuteOrDelayUntilScriptLoaded(setCalendarCollapseItemLimit, "sp.ui.applicationpages.calendar.js");
</script>
The advantage of this method is that it stays expanded even if new events are added to the calendar after the page loads, with no need to manually re-expand or refresh the page. The downside is that this solution may need updating after an upgrade since the obfuscated variable names may change (this example was confirmed working w/ SP2016 w/ the 7/2017 CU - v16.0.4561.1000).
The second method calls the calendar's built-in expandAll() function once all the events are displayed. The advantage of this method is that it leaves the "collapse" link available for the user to collapse the calendar if desired.
<script type="text/javascript">
function expandAllCalendarItems() {
var cal = SP.UI.ApplicationPages.CalendarInstanceRepository.firstInstance();
if (cal) {
cal.expandAll();
return true;
} else {
return false;
}
}
// We need to use setInterval() here so that the calendar will be expanded when it becomes available and
// after any overlaid calendar events have been rendered. An interval of 1400 seems to be sufficient for this.
// We store the return value so that we can clear it (stop the loop) once we’ve successfully expanded it.
var expItemsInterval = setInterval(function() {
var retVal = expandAllCalendarItems();
if (retVal === true) {
// Calendar was expanded - clear interval
clearInterval(expItemsInterval);
}
}, 1400);
</script>
I'm not a huge fan of this solution though since it has a couple big downsides. One is that the calendar will re-collapse if a new event is added to it after the page is loaded. More importantly, however, is the fact that we are relying on the interval being long enough to wait for all overlaid calendar events to be rendered on the page (if using calendar overlays). Therefore we have a sort of race condition here; an interval of 1400 seems to be long enough in my testing for it to work properly, but theoretically if the overlaid events take too long to render, this solution might not work. If not using overlaid calendars then this should not be an issue.
A more proper solution would probably be to use something like a MutationObserver to watch for new elements being added to the page (specifically, divs with a class of .ms-acal-mdiv), and call expandAll() again if a new one is detected. This would also solve the problem of re-expanding the calendar after a new event is added.
All in all, if showing the "collapse" link to the user is not required, I prefer the first solution. I think its the cleanest and most foolproof, since it does not rely on setInterval() and works with both regular and overlaid calendars.
Big thanks to Andrew Peacock for the original solutions here; those put me on the right track for figuring these out for SP2016.
*EDIT: Removed the jQuery dependency since it was unnecessary, and added script tags around the code. This would need to be implemented via SharePoint Designer, right under the PlaceHolderMain tag (using a SEWP or CEWP would probably work as well but I didn't test using those methods).

function expandCalendar()
{
var calendar = SP.UI.ApplicationPages.CalendarInstanceRepository.firstInstance();
if (calendar && typeof calendar.expandAll === 'function') {
calendar.expandAll();
}

Related

Angular UI Router - Parentless Child States

I think that my objective won't work with AngularUI Router - but I'm going to put it out here in case someone can prove me wrong or has an alternative solution or a workaround that solves the same problem.
The Objective
I want to display modal windows which change the url but can be opened from anywhere in my application - regardless as to which parent state is currently active. Specifically, I want the url changed so that, when the browser/device back button is pushed, the modal is closed (i.e. the app will return to whichever parent state they were using). Such a modal could be opened by the user at any time while using the app (an example would be a help window accessible from the app's main menu bar).
What I really don't want to do is copy and paste the modal state as a child of every possible parent state (i.e. register the help state as a child for each of user profile/search results/home/etc...). If there were just one such modal in the app then doing so may be an acceptable approach - but when you start introducing several globally accessible modal child states into an app then multiple child state registration starts to become a real problem.
To illustrate more clearly, here's a user story:
The user is viewing some search results (they've infinitely scrolled through several pages worth of results).
There is an action they want to perform but they're not sure how to achieve it so they click the help icon in the app's header.
A modal dialog opens which is layered above the search results they were viewing.
They search through the help and figure out what they need to do.
They press the device's back button.
The modal dialog closes, revealing the previous state they had been viewing without any loss of context.
The user performs their task and is extremely happy with themselves - and not pissed off at the app developers due to a stupid user experience design.
In the above story, the only way I can think to cause the back event to close the modal is to tie the modal to AngularUI Router's state transitions. The user would go from the search results state (url: /search-results) to the help state (url: /search-results?help) - however, in another case they may go from a user profile state (url: /profile/123) to the help state (url: /profile/123?help). The key here being, that help wasn't registered directly as a child of both the search results and profile states, but somehow independently as a type of orphaned state which could be potentially applied to any parent.
Alternative Objective
This is not my preferred solution. If it's possible to cause the browser/device back button to close a modal without changing the url then I can make these modals work independently of AngularUI Router - but I don't like this as an approach, it means having an inconsistent development approach for different types of views (and who knows, maybe in the future we'll decide that one of these modal windows should be a first-class state in its own right and this would require a change from one approach to the other - which is undesirable). I also think this is an unreliable approach as handling the back event is no trivial matter, in my experience.
This actually would be useful for many situations (for example, a user could click back to close a sub-menu or context-menu), I just don't think it's a technically viable solution - but feel free to prove me wrong. ;-)
Notes
I am aware that it is possible to open modal child states - in-fact, I've implemented this where child states are explicitly tied to a specific parent state.
This is for an app which specifically targets mobile as its main use-case. This means the back button is a fundamentally important consideration - it's normal behaviour for a mobile user to use the back button to close or cancel a dialog and I categorically do not want to have to train my app's users to click close when they're already used to using the back button.
Sorry, I have no code attempts to present - I have no idea how to get this to work or even where to start - and none of my research has shed any light on the problem (maybe I'm searching with the wrong terms?).
Thanks in advance for any assistance provided!
Edit
1. Updated the user story explanation to include concrete url/state examples for greater clarity.
Well, for anyone who has a similar need, I found a simple solution which basically goes outside of the whole routing mechanism of UI Router.
Firstly, I believe it should be possible to use the deferIntercept feature in the upcoming 0.3 release, as detailed in this SO answer. However, my solution takes a different approach. Instead of using a query parameter to identify these orphaned views (i.e. ?help), I'm using url fragment identifiers (i.e. #help). This works because the routing mechanism seems to ignore anything after the hash symbol.
I did come across a couple of gotchas before I managed to get this fully working - specifically, when dealing with non-html5 mode in the $location service. As I understand it, it's technically illegal to include a hash symbol in a fragment identifier (i.e. a url cannot contain two # symbols), so it comes with some risk, but from my testing it seems that browsers don't complain too much).
My solution involves having a hashRouter service which manages the jobs of serialising and deserialising your query data to and from the fragment identifier, and monitoring $locationChangeSuccess events to hand external changes in the url (i.e. when the browser or device's back and forward buttons are pressed).
Here's a simplified version of my service:
hashRouter.$inject = [
'$rootScope',
'$location'
];
function hashRouter($rootScope, $location) {
var service = this,
hashString = $location.hash(),
hash = fromHashString(hashString);
$rootScope.$on('$locationChangeSuccess', function (e, newUrl) {
var newHashString = getHashSection(newUrl);
if (newHashString != hashString) {
var newHash = fromHashString(newHashString);
service.hash(newHash.name, newHash.params);
}
});
service.hash = function (name, params) {
var oldHash = hash,
oldHashString = hashString;
hash = { name: name || '', params: params || {} };
hashString = toHashString(hash);
if (hashString !== oldHashString) {
var oldHashExists = oldHashString.length > 0;
if (oldHashExists) {
$rootScope.$broadcast('hashRouteRemoved', oldHash);
}
if (hashString.length > 0) {
$rootScope.$broadcast('hashRouteAdded', hash);
}
$location.hash(hashString);
if (oldHashExists) {
$location.replace();
}
}
};
return service;
function toHashString(data) {
var newHashString = '';
var name = data.name;
if (!!name) {
newHashString += encodeURIComponent(name);
}
var params = data.params;
if (!!params) {
var paramList = [];
for (var prop in params) {
var key = encodeURIComponent(prop),
value = params.hasOwnProperty(prop) ? encodeURIComponent(params[prop].toString()) : '';
paramList.push(key + '=' + value);
}
if (paramList.length > 0) {
newHashString += ':' + paramList.join('&');
}
}
return newHashString;
}
function fromHashString(urlHash) {
var parsedHash = {
name: '',
params: {}
};
if (!!urlHash && urlHash.length > 0) {
if (urlHash.indexOf(':') !== -1) {
var hashSegments = urlHash.split(':');
parsedHash.name = decodeURIComponent(hashSegments[0]);
var querySegments = hashSegments[1].split('&');
for (var i = 0; i < querySegments.length; i++) {
var pair = querySegments[i].split('=');
parsedHash.params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]) || null;
}
} else {
parsedHash.name = decodeURIComponent(urlHash);
}
}
return parsedHash;
}
function getHashSection(url) {
if (url.indexOf('#') === -1 || (url.indexOf('#!') !== -1 && url.indexOf('#') === url.lastIndexOf('#'))) {
return '';
}
var urlSegments = url.split('#');
return urlSegments[urlSegments.length - 1];
}
}
angular.module('myApp').service('hashRouter', hashRouter);
There are a couple of things to note about the service:
I rolled-my-own serialisation/deserialisation functions and they're anything but complete, so use at your own risk - or replace with something more suitable.
This depends upon using the bang part of the hash-bang (#! as opposed to #) when not in html5 mode.
If you do mess around with the serialisation/deserialisation functionality, be very careful: I found my self in a few infinite-loop scenarios which basically crashed my browser. So make sure you test thoroughly!
You still need to invoke the service whenever you open/close a dialog/menu/etc which uses the service and listen to the hashRouteAdded and hashRouteRemoved events as appropriate.
I've built this system to support only one view at a time - if you need multiple views then you'll need to customise the code somewhat (although I guess it could support nested views easily enough).
Hopefully, if anyone else needs to do the same as I've done here this can save them some time :-)
Could you use a single state as the parent for everything in the app? I do the same thing in my angular app.
$stateProvider
//root route
.state('app', {
url: '/',
templateUrl: '/scripts/app/app/views/app.html',
controller: 'appController',
resolve: {
//resolve any app wide data here
}
});
Then you can do your modal as a child of this state. That way you can always transition back to this route to get back to your app's default state (when your modal closes).
Another benefit of doing things this way is you can use the view for this route as a layout to put any markup that doesn't change from page to page (header, sidebar, etc...).

angularJS doesn't scroll to top after changing a view [duplicate]

This question already has answers here:
Changing route doesn't scroll to top in the new page
(18 answers)
Closed 8 years ago.
For example:
A user scrolls down on view A;
Then the user clicks on a link, which takes the user to view B;
The view is changes,
but the user's vertical location remains lthe same, and must scroll manually to the top of the screen.
Is it an angular bug?
I wrote a small workaround that uses jquery to scroll to the top; but I don't find the correct event to bind it to.
edit after seeing the comment:
How and WHEN do i pull myself to the top? i'm using jquery but the $viewContentLoaded event is too soon (the method runs, but the page doesn't scroll at that time)
The solution is to add autoscroll="true" to your ngView element:
<div class="ng-view" autoscroll="true"></div>
https://docs.angularjs.org/api/ngRoute/directive/ngView
Angular doesn't automatically scroll to the top when loading a new view, it just keeps the current scroll position.
Here is the workaround I use:
myApp.run(function($rootScope, $window) {
$rootScope.$on('$routeChangeSuccess', function () {
var interval = setInterval(function(){
if (document.readyState == 'complete') {
$window.scrollTo(0, 0);
clearInterval(interval);
}
}, 200);
});
});
Put it in your bootstrap (usually called app.js).
It's a pure javascript solution (it's always better not to use jQuery if it's easy).
Explanation: The script checks every 200ms if the new DOM is fully loaded and then scrolls to the top and stops checking. I tried without this 200ms loop and it sometimes failed to scroll because the page was just not completely displayed.
It seems that you understand why the problem is happening based on #jarrodek's comment.
As for a solution, you could either follow #TongShen's solution of wrapping your function in a $timeout or you can put the function call within the partial that you're loading.
<!-- New partial-->
<div ng-init="scrollToTop()">
</div>
If you view change is fired after a click event, you could also put the function call on that element. Just comes down to timing though. Just depends on how things are set up.

Hide Email on Lead Page Layout in Salesforce

I am looking at hiding Email on Leads Page Layout.
I am including it in a section already with a visualforce page but PageLayout doesn't detect that and still requires it to be on the layout.
Is there a way to remove it from the PageLayout?
I have read that you can edit a picklist but didn't find any solid examples of doing that. I also read about Field Level Accessibility but I don't think that's the way to go since everyone is part of the same role and I am only excluding the detail based off if you're the owner. I have also tried javascript but since my visualforce page is loaded in an iframe I can't access the parent document of the iframe to be able to hide or remove the value from email while viewing not as the owner.
Any thoughts would be appreciated.
Try hiding the field with JavaScript stored in a Custom Button. In the On-Click JavaScript for the button, you can include code that will be run on page load by jQuery that hides the Email field. You can then hide the actual button as well. Very much a hack approach, but it should work.
Here's some code to put in the On-Click JavaScript button (modified from Daniel Llewellyn's blog post and mgsmith's force2b blog post). Hopefully this should get you jump-started. I didn't include any code on how to hide the Email field, but you said you were already trying it through your Visualforce page, so I figured I'd leave that part to you. Cheers!
var interval;
//The main function that does all the work
function appendScript() {
// Include core jQuery library by injecting it into the DOM
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js';
head.appendChild(script);
// It takes a second to load, so put in a delay
// (we don't want to try and reference the script
// before it is actually loaded, so we store the interval
// in a global variable, and set up an interval.
// this interval dealio. This will keep running
// until jQuery has been found to be loaded and then
//clears the interval so it doesn't keep running.
interval=self.setInterval(function(){
//Check to see if jQuery has loaded
if(jQuery) {
//if jQuery has loaded, clear the interval
window.clearInterval(interval);
// Hide the Email field
// Hide the custom button
var btnName = 'buttonName';
try{
var buttons = parent.document.getElementsByName(btnName);
for (var i=0; i < buttons.length; i++) {
buttons[i].className="btnDisabled ";
buttons[i].disabled=true;
buttons[i].type='hidden';
}
} catch(e) {
// var ee = e.message || 0; alert('Error: \n\n'+e+'\n'+ee);
}
}
}, 300);
}
appendScript();

Display n time ago on various items using jquery, [issue with new elements generated after the loading the DOM]

I am using Jquery plugin http://timeago.yarp.com/ for showing time.
Issue is timeago will not take effect for dynamically generated items.
$(document).ready(function() {
$(".timeago").timeago(); // works perfectly fine for the items which are loaded on page load
//$(".timeago").live(timeago()); // gives me an error ie timeago is not defined
//$(".timeago").live($(".timeago").timeago()); // gives me an error too much recursion.
jQuery.timeago.settings.allowFuture = true;
});
From some google search I got to know something ie:
Using live is the same as using bind, except that it is limited only to the events click, dblclick, keydown, keypress, keyup, mousedown, mousemove, mouseout, mouseover, and mouseup.
Now how can do it cause I dont have any click event? How can I bind this?
.live() and .bind() assign callbacks to event. In your case, you don't have an event to assign the function to and so it fails.
You can, in theory, assign your callback to a custom event. You will however have to manually trigger the event (using .trigger()) whenever your item is generated. For example:
$("abbr.timeago").live("timeago", function() {
$(this).timeago();
});
// ... and in the bit that generates your item
$new_item.trigger("timeago")
Demo: http://jsfiddle.net/ZjuW4/9
Of course, using .live() in this situation is purely academic and does not really serve a good purpose.
If you do have access to the code that's generating the items, you can simply chain in the call to .timeago() as the items are generated, i.e. http://jsfiddle.net/ZjuW4/3/
take a look in this topic
here was discussed how to put timeago on dynamically loaded items
for example the results of an ajax request.
Activate timeago on newly added elements only
PS: allowFuture does not have anything to do with putting timeago on newly created items on your page. it just allows dates in the future (f.e. "in 3 days", "next week")

Help wrap onClick toggle checkbox into a function

I have a page with 50 hidden checkboxes, and I want to be able to toggle each checkbox by clicking on a visible link. The actual checkboxes have to stay hidden...so... Is there a better way to do this, with a JS function so I don't have to include the entire onclick in each link? And I use mootools, not jQuery.
This works to activate a checkbox:
Select
But to toggle it, this works:
onclick="if (event.target.tagName != 'INPUT') document.getElementById('field_select_temp_professional_10').checked = !document.getElementById('field_select_temp_professional_10').checked"
None of what you posted is actually mootools code, you may as well not use mootools...
Markup:
Select
js in your domready:
document.getElements("a.add_app").addEvents({
click: function(e) {
if (e.target.get("tag") != 'input') {
var checkbox = document.id("field_select_p" + this.get("data-id"));
checkbox.set("checked", !checkbox.get("checked"));
}
}
});
If you have 100+ then I suggest you look at using event delegation from mootools-more and add just one event to the parent instead of creating 100 events and storing 100 functions that deal with it.
This is coding to patterns, and it involves changing your markup to make things work. You can also make the change based upon walking the DOM in relation to the clicked item, e.g. this.getParent().getElement("input[type=checkbox]"), or something can mean you don't need to store a relative id in the element itself.

Resources