How to force a Project Center View on Project Server 2013? - sharepoint

I want that whenever a user visits a certain page with Project Center webpart in it, she should have her View already set (forced) e.g. "Summary", "Earned Value" etc.
I know that the view is bound to the user's last session, so if during her last visit the user changed the View into "Earned Value", the next one will be "Earned value".
How can I force that everytime a user opens the page with Project Center webpart, she will always open the "Summary" view?
Thanks.

This is a JavaScript solution I wrote that uses a query string parameter "viewuid" (GUID for the view) to set the view
var projCenterExt;
var JsGridSatellite;
_spBodyOnLoadFunctionNames.push("projCenterChangeView")
function projCenterChangeView()
{
if (window.location.search.toLowerCase().indexOf("viewuid") >= 0)
{
var JsGridViewUid = window.location.search.toLowerCase().split("viewuid=")[1].split("&")[0];
if (typeof projectCenterComponent !== 'undefined')
{
if (typeof JsGridSatellite === 'undefined') JsGridSatellite = projectCenterComponent.get_GridSatellite();
JsGridSatellite.LoadNewView({uid: JsGridViewUid});
}
}
}

Thanks Papa Daniel. You got us started but this would only work in Chrome. We had to add a pause in there and then it worked in I.E. Just to be clear, you need to find the GUID of the view you want to display and use that in your hyperlink.
Here is my example
http://projectserver/PWA/SitePages/ITDDash.aspx?idViewUID=38f25d41-2391-4ed4-b84e-2befec36b80b
var projCenterExt;
var JsGridSatellite;
_spBodyOnLoadFunctionNames.push("projCenterChangeView")
//console.debug("before projCenterChangeView");
function projCenterChangeView()
{
//alert("in projCenterChangeView");
//console.debug("before 3 secs");
setTimeout(function(){
//alert("in if:"+window.location.search.toLowerCase().indexOf("viewuid") );
if (document.location.search.toLowerCase().indexOf("viewuid") >= 0)
{
var JsGridViewUid = document.location.search.toLowerCase().split("viewuid=")[1].split("&")[0];
//alert("in if:"+JsGridViewUid );
if (typeof projectCenterComponent !== 'undefined')
{
if (typeof JsGridSatellite === 'undefined'){
//console.debug("JsGridSatellite kis undefined");
JsGridSatellite = projectCenterComponent.get_GridSatellite();
//alert("jjc test");
}
JsGridSatellite.LoadNewView({uid: JsGridViewUid}); //orig
}
//JsGridSatellite.LoadNewView({uid: JsGridViewUid});
}
//console.debug("after 3 secs");
}, 1000);
//alert("at end");
}

Related

How to edit Tabulator dates in the cell?

http://tabulator.info/examples/4.1
The Editable Data example above shows the use of a custom editor for the date field (example in the link is DOB). Similar examples exist in earlier tabulator versions as well as here and Github. The javascript date picker that results works perfectly for most users but not all (even if also on Chrome). So the alternate approach often attempted by the users is to try and enter the date directly into the cell. But unfortunately this is problematic --in the same way it is with the linked example. Changing the month and day isn't too bad -- but directly changing the year is very difficult. Does anyone have a potential solution? I've explored everything from blur/focus/different formats/"flatpicker"/etc - but I'm coming up empty.
The best approach to get full cross browser support would be to create a custom formatter that used a 3rd party datepicker library, for example the jQuery UI datepicker. The correct choice of date picker would depend on your needs and your existing frontend framework.
in the case of the jQuery datepicker the custom formatter could look something like this (this example uses the standard input editor, you will notice in the onRendered function it turns the standard input into the jQuery datepicker):
var dateEditor = function(cell, onRendered, success, cancel, editorParams){
var cellValue = cell.getValue(),
input = document.createElement("input");
input.setAttribute("type", "text");
input.style.padding = "4px";
input.style.width = "100%";
input.style.boxSizing = "border-box";
input.value = typeof cellValue !== "undefined" ? cellValue : "";
onRendered(function(){
input.style.height = "100%";
$(input).datepicker(); //turn input into datepicker
input.focus();
});
function onChange(e){
if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value != cellValue){
success(input.value);
}else{
cancel();
}
}
//submit new value on blur or change
input.addEventListener("change", onChange);
input.addEventListener("blur", onChange);
//submit new value on enter
input.addEventListener("keydown", function(e){
switch(e.keyCode){
case 13:
success(input.value);
break;
case 27:
cancel();
break;
}
});
return input;
}
You can then add this to a column in the column definition:
{title:"Date", field:"date", editor:dateEditor}
I couldn't get what Oli suggested to work. Then again, I might be missing something simple as I am much more of a novice. After a lot of trial+error, this is the hack kind of approach I ended up creating -- builds upon Oli's onRender suggestion but then uses datepicker's onSelect the rest of the way.
The good: The datepicker comes up regardless where in the cell the user clicks -- so the user is less tempted to try and enter manually. If the user happens to try and enter manually, they can do so.
The less-than-ideal: If the user does manually enter, the datepicker won't go away until he/she clicks elsewhere. But not a showstopper.
//Date Editor//
var dateEditor = function(cell, onRendered, success, cancel, editorParams){
var cellValue = cell.getValue(),
input = document.createElement("input");
input.setAttribute("type", "text");
input.style.padding = "4px";
input.style.width = "100%";
input.style.boxSizing = "border-box";
input.value = typeof cellValue !== "undefined" ? cellValue : "";
onRendered(function(){
$(input).datepicker({
onSelect: function(dateStr) {
var dateselected = $(this).datepicker('getDate');
var cleandate = (moment(dateselected, "YYYY-MM-DD").format("MM/DD/YYYY"));
$(input).datepicker( "destroy" );
cell.setValue(cleandate,true);
cancel();
},
});
input.style.height = "100%";
});
return input;
};
I use datepicker from bootstrap, this is my code
var dateEditor = function (cell, onRendered, success, cancel, editorParams) {
//create and style input
var editor = $("<input type='text'/>");
// datepicker
editor.datepicker({
language: 'ja',
format: 'yyyy-mm-dd',
autoclose: true,
}).on('changeDate', function() {
if(editorParams != 'row'){
editor.trigger('keyup');
}else{
editor.trigger('change');
}
});
editor.css({
"padding": "3px",
"width": "100%",
"height": "100%",
"box-sizing": "border-box",
});
editor.val(cell.getValue());
onRendered(function(){
editor.focus();
});
editor.on("blur", function (e) {
e.preventDefault();
if(editor.val() === '') {
success(cell.getValue());
}
else {
//submit new value on change
editor.on("change", function (e) {
success(editor.val());
});
}
});
return editor;
}

How to provide custom names for page view events in Azure App Insights?

By default App Insights use page title as event name. Having dynamic page names, like "Order 32424", creates insane amount of event types.
Documentation on the matter says to use trackEvent method, but there are no examples.
appInsights.trackEvent("Edit button clicked", { "Source URL": "http://www.contoso.com/index" })
What is the best approach? It would be perfect to have some sort of map/filter which would allow to modify event name for some pages to the shared name, like "Order 23424" => "Order", at the same time to leave most pages as they are.
You should be able to leverage telemetry initializer approach to replace certain pattern in the event name with the more "common" version of that name.
Here is the example from Application Insights JS SDK GitHub on how to modify pageView's data before it's sent out. With the slight modification you may use it to change event names based on their appearance:
window.appInsights = appInsights;
...
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType) {
// this statement removes url from all page view documents
telemetryItem.url = "URL CENSORED";
}
// To set custom properties:
telemetryItem.properties = telemetryItem.properties || {};
telemetryItem.properties["globalProperty"] = "boo";
// To set custom metrics:
telemetryItem.measurements = telemetryItem.measurements || {};
telemetryItem.measurements["globalMetric"] = 100;
});
});
// end
...
appInsights.trackPageView();
appInsights.trackEvent(...);
With help of Dmitry Matveev I've came with the following final code:
var appInsights = window.appInsights;
if (appInsights && appInsights.queue) {
function adjustPageName(item) {
var name = item.name.replace("AppName", "");
if (name.indexOf("Order") !== -1)
return "Order";
if (name.indexOf("Product") !== -1)
return "Shop";
// And so on...
return name;
}
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType || envelope.name === Microsoft.ApplicationInsights.Telemetry.PageViewPerformance.envelopeType) {
// Do not track admin pages
if (telemetryItem.name.indexOf("Admin") !== -1)
return false;
telemetryItem.name = adjustPageName(telemetryItem);
}
});
});
}
Why this code is important? Because App Insights use page titles by default as Name for PageView, so you would have hundreds and thousands of different events, like "Order 123132" which would make further analysis (funnel, flows, events) meaningless.
Key highlights:
var name = item.name.replace("AppName", ""); If you put your App/Product name in title, you probably want to remove it from you event name, because it would just repeat itself everywhere.
appInsights && appInsights.queue you should check for appInsights.queue because for some reason it can be not defined and it would cause an error.
if (telemetryItem.name.indexOf("Admin") !== -1) return false; returning false will cause event to be not recorded at all. There certain events/pages you most likely do not want to track, like admin part of website.
There are two types of events which use page title as event name: PageView
and PageViewPerformance. It makes sense to modify both of them.
Here's one work-around, if you're using templates to render your /orders/12345 pages:
appInsights.trackPageView({name: TEMPLATE_NAME });
Another option, perhaps better suited for a SPA with react-router:
const Tracker = () => {
let {pathname} = useLocation();
pathname = pathname.replace(/([/]orders[/])([^/]+), "$1*"); // handle /orders/NN/whatever
pathname = pathname.replace(/([/]foo[/]bar[/])([^/]+)(.*)/, "$1*"); // handle /foo/bar/NN/whatever
useEffect(() => {
appInsights.trackPageView({uri: pathname});
}, [pathname]);
return null;
}

Detecting a category page in the isCategoryPage method

I currently have an SCA website that has sub categories that need to display as a category page, and not a Product listing page. (i.e. display the categories, not the products).
Currently, I have modified the isCategoryPage to override the Facets.Views.isCategoryPage such that it does this correctly. However, when doing a search on the site - it breaks that page with a blank page.
I am currently stuck at figuring out how to detect if I am on a search page rather than a category page.
The code is thus:
...
// #Overrides Facets.Views.isCategoryPage
isCategoryPage: function isCategoryPage(translator) {
var currentFacets = translator.getAllFacets();
var categories = translator.getCategoryPath();
if (<--IsSearchPage() === true --->) {
return (_.keys(categories[categories.length-1].categories).length !== 0);
} else {
return (currentFacets.length === 1 &&
currentFacets[0].id === 'category' &&
categories &&
CategoryHelper.showCategoryPage(categories)
);
}
},
...
As you can see the if statement is where I need a bit of help.
if (<--IsSearchPage() === true --->) {
What method, function, code would detect if the page is a search page. Or if the page url has /search in the url. (either would work).
Thank you.
The proper update, after much trial and error:
isCategoryPage: function isCategoryPage(translator) {
var currentFacets = translator.getAllFacets();
var categories = translator.getCategoryPath();
if (categories) {
return (_.keys(categories[categories.length-1].categories).length !== 0);
} else {
return (currentFacets.length === 1 &&
currentFacets[0].id === 'category' &&
categories &&
CategoryHelper.showCategoryPage(categories)
);
}
},

Multiple Tab Chrome Extension Issue

I've created a basic extension that searches Google if the URL/HTML content fulfill certain requirements. It works for the most part, but fails miserably when there are multiple instances of the extension. For example, if I load tab A and then tab B, but click on the page action for tab A, I will be directed to a search of tab B's content.
I don't know how to silo the script to each tab, so that clicking tab A's page action will always result in a search for tab A stuff. How can that be done? I'd appreciate your suggestions!
background.js
title = "";
luckySearchURL = "http://www.google.com/search?btnI=I%27m+Feeling+Lucky&ie=UTF-8&oe=UTF-8&q=";
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.title != "") {
title = request.title;
sendResponse({confirm: "WE GOT IT."});
}
});
chrome.tabs.onUpdated.addListener(function(tabId, change, tab) {
if (change.status === "complete" && title !== "") {
chrome.pageAction.show(tabId);
}
});
chrome.pageAction.onClicked.addListener(function(tab) {
chrome.tabs.create({url: luckySearchURL + title})
})
contentscript.js
function getSearchContent() {
url = document.URL;
if (url.indexOf("example.com/") > -1)
return "example";
}
if (window === top) {
content = getSearchContent();
if (content !== null) {
chrome.runtime.sendMessage({title: content}, function(response) {
console.log(response.confirm); })
};
}
You could do something like store the title with its associated tabId, that way when you click on the pageAction it uses the correct title. The changes would be just these:
background.js
title= [];
[...]
chrome.runtime.onMessage.addListener(function(request,sender,sendResponse){
if (request.title != "") {
title.push({tabId:sender.tab.id, title:request.title});
sendResponse({confirm: "WE GOT IT."});
}
});
[...]
chrome.pageAction.onClicked.addListener(function(tab) {
title.forEach(function(v,i,a){
if(v.tabId == tab.id){
chrome.tabs.create({url: luckySearchURL + v.title});
// Here I am going to remove it from the array because otherwise the
// array would grow without bounds, but it would be better to remove
// it when the tab is closed so that you can use the pageAction more
// than once.
a.splice(i,1);
}
});
});
You're facing this issue because of window === top. So your title variable gets its value from the last opened tab. So if B is opened after A, title gets its value from B. Try this: Detect Tab Id which called the script, fetch the url of that tab, which then becomes your title variable. As below:
chrome.pageAction.onClicked.addListener(function(tab) {
chrome.tabs.query({active:true},function(tabs){
//this function gets tabs details of the active tab, the tab that clicked the pageAction
var urltab = tabs[0].url;
//get the url of the tab that called this script - in your case, tab A or B.
chrome.tabs.create({url: urltab + title});
});
});

Extending or modifying the SharePoint Datasheet view

Has anyone discovered a way to extend or modify the functionality of the SharePoint Datasheet view (the view used when you edit a list in Datasheet mode, the one that looks like a basic Excel worksheet)?
I need to do several things to it, if possible, but I have yet to find a decent non-hackish way to change any functionality in it.
EDIT: An example of what I wish to do is to enable cascading filtering on lookup fields - so a choice in one field limits the available choices in another. There is a method to do this in the standard view form, but the datasheet view is completely seperate.
Regards
Moo
I don't think you can modify it in any non-hackish way, but you can create a new datasheet view from scratch. You do this by creating a new ActiveX control, and exposing it as a COM object, and modifying the web.config file to make reference to the new ActiveX control.
There's an example here:
Creating a custom datasheet control.
Actually, you can do this. Here is a code snippet I stripped out of someplace where I am doing just what you asked. I tried to remove specifics.
var gridFieldOverrideExample = (function (){
function fieldView(ctx){
var val=ctx.CurrentItem[curFieldName];
var spanId=curFieldName+"span"+ctx.CurrentItem.ID;
if (ctx.inGridMode){
handleGridField(ctx, spanId);
}
return "<span id='"+spanId+"'>"+val+"</span>";
}
function handleGridField(ctx, spanID){
window.SP.SOD.executeOrDelayUntilScriptLoaded(function(){
window.SP.GanttControl.WaitForGanttCreation(function (ganttChart){
var gridColumn = null;
var editID = "EDIT_"+curFieldName+"_GRID_FIELD";
var columns = ganttChart.get_Columns();
for(var i=0;i<columns.length;i++){
if(columns[i].columnKey == curFieldName){
gridColumn = columns[i];
break;
}
}
if (gridColumn){
gridColumn.fnGetEditControlName = function(record, fieldKey){
return editID;
};
window.SP.JsGrid.PropertyType.Utils.RegisterEditControl(editID, function (ctx) {
editorInstance = new SP.JsGrid.EditControl.EditBoxEditControl(ctx, null);
editorInstance.NewValue = "";
editorInstance.SetValue = function (value) {
_cellContext = editorInstance.GetCellContext();
_cellContext.SetCurrentValue({ localized: value });
};
editorInstance.Unbind = function () {
//This happens when the grid cell loses focus - hide controls here, do cleanup, etc.
}
//Below I grabbed a reference to the original 'BindToCell' function so I can prepend to it by overwriting the event.
var origbtc = editorInstance.BindToCell;
editorInstance.BindToCell = function(cellContext){
if ((cellContext.record) &&
(cellContext.record.properties) &&
(cellContext.record.properties.ID) &&
(cellContext.record.properties.ID.dataValue)){
editorInstance.ItemID = cellContext.record.properties.ID.dataValue;
}
origbtc(cellContext);
};
//Below I grabbed a reference to the original 'OnBeginEdit' function so I can prepend to it by overwriting the event.
var origbte = editorInstance.OnBeginEdit;
editorInstance.TargetID;
editorInstance.OnBeginEdit = function (cellContext){
this.TargetID = cellContext.target.ID;
/*
. . .
Here is where you would include any custom rendering
. . .
*/
origbte(cellContext);
};
return editorInstance;
}, []);
}
});
},"spgantt.js");
}
return{
fieldView : fieldView
}
})();
(function () {
function OverrideFields(){
var overrideContext = {};
overrideContext.Templates = overrideContext.Templates || {};
overrideContext.Templates.Fields = {
'FieldToOverride' : {
'View': gridFieldOverrideExample.fieldView
}
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
}
ExecuteOrDelayUntilScriptLoaded(OverrideFields, 'clienttemplates.js');
})();
Also, there are a couple of other examples out there. Sorry, I don't have the links anymore:

Resources