Get shared pages using localized component - tridion

I'm currently working on a event handler, which will on save-event, save the latest revision date of the page and it's components into a custom database, and this is working, but i'm facing some performance issues.
Let me give you an example, for how it's working:
If a component is saved/updated (changes are made), then I want to update all pages using this component in my custom database.
Basic blueprint setup:
200 contains components
400 translation publication for components (inherent components from 200)
500 master publication for pages
600 local site publication inherent components from 400 and pages from 500)
So lets say we have component X in 200, this component is included in in a page in 500, X is localized in 400 (let us call it X(l)) and the page in 600 is NOT localized ( i.e it will use the component x(l))
So now to my question:
In my save event handler, i check what pages X(l) are included in , by using :
var filter = new UsingItemsFilter(component.Session)
{
IncludeLocalCopies = true,
ItemTypes = new[] { ItemType.Page }
};
but this will only give me references to pages from 500.
So what i then do (which feels kind of stupid) is to
create a tcm id of all pages in child publications(by looping over them) that inherent this page (now im using some psuedocodelike coding):
foreach(var 500page in foundPages)
foreach(var publication in publications){
if(subject.Session.IsExistingObject(
new TcmUri(page.Id.ItemId,page.Id.ItemType,publication.Id.ItemId)
))
{
someListThatWillContainAllChildPagesFrom500.Add(TheAboveTcmUri);
}
}
And then for each page, check the components in that page, and finally save this state to my custom database.
So the question is, is there a way by to query for all pages, that make use of the localized component, by using the TOM.NET api (no not CoreServiceClient)?
Is there a better way to find child publications of a publication, so I only need to check for pages in the childPublication?
Br Martin

The following code sample should give you the shared items of a specific local/localized item along with the item. BluePrintNodesFilter was introduced in 2013, however in earlier version BluePrintFilter should work in similar way.
SystemManager systemManager = session.SystemManager;
BluePrintNodesFilter filter = new BluePrintNodesFilter(session)
{
BaseColumns = ListBaseColumns.Id,
ForItem = page
};
IEnumerable<BluePrintNode> allNodes = systemManager.GetBluePrintNodes(filter);
IEnumerable<RepositoryLocalObject> sharedAndLocal = (from node in allNodes
where node.Item != null
where node.Item.OwningRepository.Id == page.OwningRepository.Id
select node.Item).ToArray();

Related

Handling browser refresh with query filters from page parameters

I have two data sources
Assets
Locations
Assets has a One to Many relation with Locations
Assets has a query builder data source, AssetLocFiltered, that is set with the following:
ParentLocationKey =:ParentLocationKey
ParentLocationKey is the relation field for the locations table.
On the home page you select a location and then click a button that passes the location ID to a parameter on the ShopPageDemo page. with the following code
app.pages.ShopPageDemo.properties.ParentLocationKey =
widget.datasource.item.Id;
console.log(widget.datasource.item.Id);
app.showPage(app.pages.ShopPageDemo);
One the ShopPageDemo Page there is a table view of the AssetLocFiltered that sets the query parameter in the ondataload event with the following code:
widget.root.datasource.query.parameters.ParentLocationKey =
widget.root.properties.ParentLocationKey;
widget.root.datasource.load();
This works great. The problem is when i hit the browser refresh it seems like it clears the property in ShopPageDemo. How do I handle this browser refresh issue? I am not really sure where to start.
There is more than one way to do this; However, the approach I usually take involves deep linking. In your case, you'll need to do something like this:
1.) On the button that takes you to the next page, add this code:
var params = {
key: widget.datasource.item.Id
};
var page = app.pages.ShopPageDemo;
app.showPage(page);
google.script.history.replace(null, params, page.name);
2.) On the ShopPageDemo onAttach event handler, add the following:
google.script.url.getLocation(function(location) {
widget.root.properties.ParentLocationKey = location.parameter.key;
var ds = widget.datasource;
ds.query.parameters.ParentLocationKey = widget.root.properties.ParentLocationKey;
ds.load();
});
For better performance, I would set the ShopPageDemo datasource to NOT load automatically and also to unload its data on the onDetach event handler like this: widget.datasource.unload().
Reference:
1. https://developers.google.com/apps-script/guides/html/reference/history
2. https://developers.google.com/apps-script/guides/html/reference/url

Is it possible to create multiple draft items on createdatasource?

I am building an application that will have the ability to create agenda items to discuss in a meeting. The agenda item might include one or more attachments to discuss so there is a one to many relation between the AgendaItems and the AgendaDocs models. So far, I have an insert form that looks like this:
The "Select File" button is a drive picker and the code I have inside the onDocumentSelect event is the following:
var docs = result.docs;
var createDataSource = app.datasources.AgendaDocs.modes.create;
for(var i=0; i<docs.length-1; i++){
var uniqueDraft = createDataSource.item;
createDataSource.items.push(uniqueDraft);
}
for(var i=0; i<createDataSource.items.length-1; i++){
var draft = createDataSource.item;
createDataSource.items[i].DocTitle = docs[i].name;
createDataSource.items[i].DocURL = docs[i].url;
createDataSource.items[i].DriveID = docs[i].id;
}
console.log(createDataSource.items);
The code is supposed to fill out the the List widget below the "Select File" button, but as how you see, the three items are the same. The datasource of the List widget is "AgendaDocs.modes.create" and the datasource of the insert form is "AgendaItems.modes.create".
Reading the official documentation from appmaker, makes me think it is possible since the properties of "CreateDataSource" includes "items". I need help from an expert here. Is this possible? Am I using the wrong approach?
First things first, it seems that you are trying to create records from different models and relationship between them in a one call... at this time App Maker is not that smart to digest such a complex meal. Most likely you'll need to break your flow into multiple steps:
Create (persist) Agenda Item
Create AgendaDocs records and relation with AgendaItem
Similar flow is implemented in Travel Approval template app, but it is not exactly the same as yours, since it doesn't create associations in batches.
Going back to the original question. Yep, it is possible to have multiple drafts, but not with the Create Datasource. You are looking for Manual Save Mode. Somewhere in perfect world your code would look similar to this:
// AgendaItems in Manual Save mode
var agendaDs = app.datasources.AgendaItems;
// this line will create item on client and automatically push it
// to ds.items and set ds.item to it.
agendaDs.createItem();
var agendaDraft = agendaDs.item;
// Field values can be populated from UI via bindings...
agendaDraft.Type = 'X';
agendaDraft.Description = 'Y';
// onDocumentSelect Drive Picker's event handler
var docsDs = agendaDs.relations.AgendaDocs;
result.docs.forEach(function(doc) {
// this line will create item on client and automatically push it
// to ds.items and set ds.item to it...however it will throw an exception
// with this message:
// Cannot save a foreign key association for the 'AgendaItem'
// relation because the target record has not been persisted
// to the server. To fix this, call saveChanges()
// on the data source for that record's model: AgendaItem
docsDs.createItem();
var docDraft = docsDs.item;
docDraft.DocTitle = doc.name;
docDraft.DocURL = doc.url;
docDraft.DriveID = doc.id;
});
// submit button click
agendaDraft.saveChanges();

Relational Query - 2 degrees away

I have three models:
Timesheets
Employee
Manager
I am looking for all timesheets that need to be approved by a manager (many timesheets per employee, one manager per employee).
I have tried creating datasources and prefetching both Employee and Employee.Manager, but I so far no success as of yet.
Is there a trick to this? Do I need to load the query and then do another load? Or create an intermediary datasource that holds both the Timesheet and Employee data or something else?
You can do it by applying a query filter to the datasource onDataLoad event or another event. For example, you could bind the value of a dropdown with Managers to:
#datasource.query.filters.Employee.Manager._equals
- assuming that the datasource of the widget is set to Timesheets.
If you are linking to the page from another page, you could also call a script instead of using a preset action. On the link click, invoke the script below, passing it the desired manager object from the linking page.
function loadPageTimesheets(manager){
app.showPage(app.pages.Timesheets);
app.pages.Timesheets.datasource.query.filters.Employee.Manager._equals = manager;
app.pages.Timesheets.datasource.load();
}
I would recommend to redesign your app a little bit to use full power of App Maker. You can go with Directory Model (Manager -> Employees) plus one table with data (Timesheets). In this case your timesheets query can look similar to this:
// Server side script
function getTimesheets(query) {
var managerEmail = query.parameters.ManagerEmail;
var dirQuery = app.models.Directory.newQuery();
dirQuery.filters.PrimaryEmail._equals = managerEmail;
dirQuery.prefetch.DirectReports._add();
var people = dirQuery.run();
if (people.length === 0) {
return [];
}
var manager = people[0];
// Subordinates lookup can look fancier if you need recursively
// include everybody down the hierarchy chart. In this case
// it also will make sense to update prefetch above to include
// reports of reports of reports...
var subortinatesEmails = manager.DirectReports.map(function(employee) {
return employee.PrimaryEmail;
});
var tsQuery = app.models.Timesheet.newQuery();
tsQuery.filters.EmployeeEmail._in = subortinatesEmails;
return tsQuery.run();
}

Render Node by Id in Umbraco 4.11

TL;DR How do you render a content item by Node Id.
I'm using Widget Grid admin extension for Umbraco to configure a content manageable widget area and I'm stuck trying to render the configured widgets.
I have a list of node id's that I want to render on the page but I can for the life of me work out how to do this.
Any guidance is greatly appreciated! Thanks.
UPDATE
Xml contained within the Models property
<WidgetGrid>
<col1> <nodeId>4839</nodeId> <nodeId>4844</nodeId> <nodeId>4845</nodeId> </col1>
</WidgetGrid>
Once I get down to col1 it seems to cease being of type DynamicXml and just spits it out as a string... No idea.
Presumably you're doing this from within a macro, and I'm going to presume you have a multi-node picker set to store the Id values as CSV:
#{
string[] widgetIds = ((string)Model.Widgets).Split(',');
foreach (var id in widgetIds)
{
var widget = Model.NodeById(id);
#RenderPage("~/macroscripts/widgets/" + widget.NodeTypeAlias
+ ".cshtml", widget);
}
}
This will let you select different types of widget in your multi-node picker, and have a different view for each widget.
This also uses the dynamic approach but you could easily adapt this by swapping the first line for:
string[] podIds = CurrentModel.GetProperty<string>("widgets").Split(',');
Given that the property is stored as XML, you can use the notation as described in this post (http://umbraco.com/follow-us/blog-archive/2011/2/28/umbraco-razor-feature-walkthrough-%E2%80%93-part-3):
foreach (var id in Model.widgets.col1[0].nodeId)
{
...
}

Retrieving a list of Tridion 2009 components with a specific value and schema without using search

I would like to create a .NET page residing on the CMS server that shows all components that are based on a specific Schema(tcm:3-3-8) and from a specific Publication(tcm:0-3-1) including BluePrinted and Localized items, but only if they have the value "http://www.google.com" for the field "URL" in that Schema.
Is this possible, without using the search service as this is rather slow and unreliable?
Your search might be slow because of not indexing the search collection.
You should do indexing the search collection on regular intervals for better and fast results.
That's an expensive operation to do because of the cost of opening each individual component to check the value of a field, but certainly do-able.
Get the schema object
Get a list of components that use this schema (WhereUsed on the schema with filter on ItemType = component)
Open each component and check the value for the field(s), add to a List<Component> if it matches
Display list (possibly using a ASP.NET GridView)
I have not had any chance to test it, but something like this
Common common = new Common();
TDSE tdse = new TDSE();
ListRowFilter ComponentFilter = tdse.CreateListRowFilter();
Schema schema = (Schema)common.getObject("tcm:126-238630-8", ItemType.ItemTypeSchema);
ComponentFilter.SetCondition("ItemType", ItemType.ItemTypeComponent);
ComponentFilter.SetCondition("Recursive", true);
XDocument doc = common.ReadXML(schema.Info.GetListUsingItems(ListColumnFilter.XMLListID, ComponentFilter));
List<Component> MatchedComponents = new List<Component>();
XmlNamespaceManager NS = new XmlNamespaceManager(new NameTable());
NS.AddNamespace("tcm", "http://www.tridion.com/ContentManager/5.0");
NS.AddNamespace("Content", "uuid:4432F3C3-9F3E-45E4-AE31-408C5C46E2BF");
foreach (XElement component in doc.XPathSelectElements("/tcm:ListUsingItems/tcm:Item", NS))
{
Component comp = common.getComponent(component.Attribute("ID").Value);
XDocument compDoc = common.ReadXML(comp.GetXML(XMLReadFilter.XMLReadData));
foreach (XElement compNode in compDoc.XPathSelectElements("/tcm:Component/tcm:Data/tcm:Content/Content:Content/Content:feederUrl", NS))
{
MatchedComponents.Add(comp);
}
}

Resources