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();
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();
}
My question is simple. Let's say I have 2 List Controls. 1 of Users and 1 of Tickets. (The 2 Sources)
And I have a DataGrid (the target). Is possible to select 1 user and 1 ticket in the 2 list mentioned before and drag & drop them at the same time to the DataGrid?
I know it's not as simple as they are going to get automatically mixed.. If it is possible... I would have to use the DragEnter Event of the Datagrid or something to mix them and create my dataProvider. But I don't know how you can drag & drop 2 items at the same time from different sources. It is possible with one source.. But no idea of how to do it with 2 sources.
Any Help would be really appreciated.
Thanks in advance
Yes, I think that would still be possible.
Your option would be:
(click) select the item on the Users and click (select) the item on the Tickets.
From whichever list you started the drag, you would still be able to populate the datagrid with the items from dragEvent and the selected item on the list.
//
boolUsers:Boolean;
On your datagrid:
private function dataGrid_dragDrop(evt:DragEvent) :void
{
// This will get the items from the list where you initiated the drag.
var objDrag:Object;
objDrag = evt.dragSource.dataForFormat("items");
// Depending on where the drag was initiated, get the items from the list.
var objList:Object;
if(boolUsers)
{
objList = listTickets.selectedItem;
}
else
{
objList = listUsers.selectedItem;
}
boolUsers = false;
}
And you would need to define a dragstart event for both your list
private function listUsers_dragStart(evt:DragEvent) :void
{
boolUsers = true;
}
I've gotten a checkbox header renderer to work well with flat DPs, but a
hierarchical collection view is another story. On click, I want it to select all
checkboxes in a given column. Here is my code:
var dp:HierarchicalCollectionView = _dataGrid.dataProvider as
HierarchicalCollectionView;
var testDp:GroupingCollection = dp.source as GroupingCollection;
var rawDp:ArrayCollection = testDp.source as ArrayCollection;
for(var i:int=0 ; i < rawDp.length ; i++){
rawDp[i][_dataField] = cb.selected;
}
It selects all checkboxes on the 2nd level of data, but doesn't select the top
level of data. What am I missing here? I can't seem to find it.
Any tips are greatly appreciated. Thank you.
For hierarchical data you have to use a cursor which iterates over all levels of the hierarchical data.
var dp:IHierarchicalCollectionView = _dataGrid.hierarchicalCollectionView;
var cursor:IViewCursor= dp.createCursor();
while (!cursor.afterLast)
{
cursor.current[_dataField] = cb.selected;
cursor.moveNext();
}
Howerver, this works only with nodes that have previously been opened. So either expand all nodes with _dataGrid.expandAll() (you can collapse them afterwards since the nodes only have to be opened once) or iterate your hierarchical data manually:
function setCheckBoxValue(children:ArrayCollection, value:Boolean):void
{
for each (var child:Object in children)
{
if (child.hasOwnProperty("children") && child["children"])
setCheckBoxValue(child["children"], value);
child[_dataField] = value;
}
}
var myDataProvider:HierarchicalData = /* your data provider */;
// Call it like this...
setCheckBoxValue(myDataProvider.source, cb.selected);
Update: To answer your second question...
Create a new CheckBoxColumn which extends AdvancedDataGridColumn. You can use it to preconfigure your headerRenderer and itemRenderer.
In your custom item renderer you get hold of your column like this:grid = AdvancedDataGrid(listData.owner);
column = grid.columns[listData.columnIndex] as CheckBoxColumn;
Do the same in your header renderer.
Whenever the CheckBox value in one of your item renderers changes dispatch a event through your column. Something like: column.dispatchEvent(new Event("checkBoxValueChanged"));
Your header render should add an event listener to the column for the "checkBoxValueChanged" event (or whatever you call it). Whenever that event is fired loop through your data provider and update the headers CheckBox accordingly.
In theory that should work. HTH
I have table with pageId, parentPageId, title columns.
Is there a way to return unordered nested list using asp.net, cte, stored procedure, UDF... anything?
Table looks like this:
PageID ParentId Title
1 null Home
2 null Products
3 null Services
4 2 Category 1
5 2 Category 2
6 5 Subcategory 1
7 5 SubCategory 2
8 6 Third Level Category 1
...
Result should look like this:
Home
Products
Category 1
SubCategory 1
Third Level Category 1
SubCategory 2
Category 2
Services
Ideally, list should contain <a> tags as well, but I hope I can add it myself if I find a way to create <ul> list.
EDIT 1: I thought that already there is a solution for this, but it seems that there isn't. I wanted to keep it simple as possible and to escape using ASP.NET menu at any cost, because it uses tables by default. Then I have to use CSS Adapters etc.
Even if I decide to go down the "ASP.NET menu" route I was able to find only this approach: http://aspalliance.com/822 which uses DataAdapter and DataSet :(
Any more modern or efficient way?
Using linq2sql you could do:
List<PageInfo> GetHierarchicalPages()
{
var pages = myContext.PageInfos.ToList();
var parentPages = pages.Where(p=>p.ParentId == null).ToList();
foreach(var page in parentPages)
{
BuildTree(
page,
p=> p.Pages = pages.Where(child=>p.pageId == child.ParentId).ToList()
);
}
}
void BuildTree<T>(T parent, Func<T,List<T>> setAndGetChildrenFunc)
{
foreach(var child in setAndGetChildrenFunc(parent))
{
BuildTree(child, setAndGetChildrenFunc);
}
}
Assuming you define a Pages property in the PageInfo like:
public partial class PageInfo{
public List<PageInfo> Pages{get;set;}
}
The processing to get it on a hierarchy is happening on web application side, which avoids extra load on the sql server. Also note that this type of info is a perfect candidate to cache.
You can do the render as Rex mentioned. Alternatively you could expand a bit on this implementation and make it support the hierarchy interfaces and use asp.net controls.
Update 1: For the rendering variation you asked on a comment, you can:
var sb = new System.IO.StringWriter();
var writer = new HtmlTextWriter(sb);
// rex's rendering code
var html = sb.ToString();
Best practice would be to do this using IHierarchyData and IHierarchalEnumerable and DataBind to a custom control which inherits from HierarchalDataBoundControl (this is the base for controls like TreeView).
However, let's try for a quick-and-dirty, not-especially-efficient, simple example in c#:
//class to hold our object graph in memory
//this is only a good idea if you have a small number of items
//(less than a few thousand)
//if so, this is a very flexible and reusable way to represent your tree
public class Page
{
public string Title {get;set;}
public int ID {get;set;}
public Collection<Page> Pages = new Collection<Page>();
public Page FindPage(int id)
{
return FindPage(this, id);
}
private Page FindPage(Page page, int id)
{
if(page.ID == id)
{
return page;
}
Page returnPage = null;
foreach(Page child in page.Pages)
{
returnPage = child.FindPage(id);
if(returnPage != null)
{
break;
}
}
return returnPage;
}
}
//construct our object graph
DataTable data = SelectAllDataFromTable_OrderedByParentIDAscending();
List<Page> topPages = new List<Page>();
foreach(DataRow row in data.Rows)
{
Page page = new Page();
page.Title = (string)row["Title"];
page.ID = (int)row["PageID"];
if(row["ParentID"] == null)
{
topPages.Add(page);
}
else
{
int parentID = (int)row["ParentID"];
foreach(Page topPage in topPages)
{
Page parentPage = topPage.FindPage(parentID);
if(parentPage != null)
{
parentPage.Pages.Add(page);
break;
}
}
}
}
//render to page
public override void Render(HtmlTextWriter writer)
{
writer.WriteFullBeginTag("ul");
foreach(Page child in topPages)
{
RenderPage(writer, child);
}
writer.WriteEndTag("ul");
}
private void RenderPage(HtmlTextWriter writer, Page page)
{
writer.WriteFullBeginTag("li");
writer.WriteBeginTag("a");
writer.WriteAttribute("href", "url");
writer.Write(HtmlTextWriter.TagRightChar);
writer.Write(page.Title);
writer.WriteEndTag("a");
if(page.Pages.Count > 0)
{
writer.WriteFullBeginTag("ul");
foreach(Page child in page.Pages)
{
RenderPage(writer, child);
}
writer.WriteEndTag("ul");
}
writer.WriteEndTag("li");
}
This should get you started.
with x (pageID, title)
as (
select cast(title as varchar(100)),pageID
from pages
where parentID is null
union all
select cast(x.title||' - '||e.title as varchar(100)),
e.pageID
from pages e, x
where e.parentID = x.pageID
)
select title as title_tree
from x
order by 1
Output:
TITLE_TREE
Home
Products
Services
Products - Category 1
Products - Category 2
Products - Category 2 - Subcategory 1
Products - Category 2 - Subcategory 1 - Third Level Category 1
Products - Category 2 - Subcategory 2
Have you considered getting XML output from SQL Server using SELECT ... FOR XML EXPLICIT? Your data seems set up perfectly for that.
For an example:
http://www.eggheadcafe.com/articles/20030804.asp
If you want to pursue I could work through an example.
RexM - firstly I must state that I'm a front-end developer so can't even touch you for skill and knowedge of coding C#. However - I did implement your solution using the Page object and encountered a problem. Yes, sorry I'm a "pleaseSendMeTheCode" leech in this instance, but neverless, thought it was important to detail the "bug".
I'm building a site that uses a nested UL to display menu items and allows the user to re-sort the menu however they want.
My menu has the following data fields: pageID, parentID, pageOrder, pageTitle
Page order refers to the order in which the pages appear in a node.
So my query for SelectAllDataFromTable_OrderedByParentIDAscending();was:
SELECT * FROM [pages] ORDER BY [parentID] ASC, [pageOrder] ASC
I then use jsTree to make the menu items draggable and droppable.
I re-ordered a few pages and discovered a bug:
Say my structure is like so:
home
cars
usa
muscle cars
suvs
europe
colours
directions
vertical
horizontal
up
down
If I move "cars" (and all it's children) inside "down", the children of "cars" no longer display in the menu. That's the "bug".
I have checked the db and parentID and pageOrder are all correct under "cars", I also tried changing my SQL query, starting from scratch, all sorts of testing directly on the DB (all the above with jsTree turned off so I can see the basic nested UL) - but with no success.
Just wondering, as I've seen other forums pointing to this page for solutions to turning hierarchical sql data into nested UL's, it might be worth somebody looking into it.
As my whole site is based on the use of Javascript I've now implemented a Jquery.ajax solution (which, very badly commented, is on my site here) to build the nested UL but as I said, just flagging as potential problem.
Thanks very much though for a kick start in my own finding of a solution!