PowerShell provider relative path tab-completion issue - powershell

I've implemented a simple PowerShell NavigationCmdletProvider.
For those who don't know, this means I can create a snap-in with a cmdlet which is effectively a virtual filesystem drive; this drive can be mounted and navigated into from PowerShell like any normal folder. Each action against the drive (e.g., check if a path points to a valid item, get a list of names of child items in a folder, etc.) is mapped to a method of the .NET class inherited from the NavigationCmdletProvider class.
I'm facing a problem with tab-completion, and would like to find a solution. I've found that tab-completion gives incorrect results when using relative paths. For absolute paths, it works fine.
For those who don't know, tab completion for a NavigationCmdletProvider works through PowerShell calling the GetChildNames method, which is overridden from the NavigationCmdletProvider class.
--Demonstration of the issue--
Assume I have a provider, 'TEST', with the following folder hierarchy:
TEST::child1
TEST::child1\child1a
TEST::child1\child1b
TEST::child2
TEST::child2\child2a
TEST::child2\child2b
TEST::child3
TEST::child3\child3a
TEST::child3\child3b
Absolute paths:
If I type "dir TEST::child1\" and press tab a few times, it gives me the expected results:
> dir TEST::child1\child1a
> dir TEST::child1\child1b
Relative paths:
First, I navigate to "TEST::child1":
> cd TEST::child1
Then, if I type "dirspace" and press tab a few times, it gives me incorrect results:
> dir .\child1\child1a
> dir .\child1\child1b
I expect to see these instead:
> dir .\child1a
> dir .\child1b
Is this a bug in PowerShell, or am I doing something wrong?
Here's the complete, self-contained code for the provider:
[CmdletProvider("TEST", ProviderCapabilities.None)]
public class MyTestProvider : NavigationCmdletProvider
{
private Node m_Root;
private void ConstructTestHierarchy()
{
//
// Create the nodes
//
Node root = new Node("");
Node child1 = new Node("child1");
Node child1a = new Node("child1a");
Node child1b = new Node("child1b");
Node child2 = new Node("child2");
Node child2a = new Node("child2a");
Node child2b = new Node("child2b");
Node child3 = new Node("child3");
Node child3a = new Node("child3a");
Node child3b = new Node("child3b");
//
// Construct node hierarchy
//
m_Root = root;
root.AddChild(child1);
child1.AddChild(child1a);
child1.AddChild(child1b);
root.AddChild(child2);
child2.AddChild(child2a);
child2.AddChild(child2b);
root.AddChild(child3);
child3.AddChild(child3a);
child3.AddChild(child3b);
}
public MyTestProvider()
{
ConstructTestHierarchy();
}
protected override bool IsValidPath(string path)
{
return m_Root.ItemExistsAtPath(path);
}
protected override bool ItemExists(string path)
{
return m_Root.ItemExistsAtPath(path);
}
protected override void GetChildNames(string path, ReturnContainers returnContainers)
{
var children = m_Root.GetItemAtPath(path).Children;
foreach (var child in children)
{
WriteItemObject(child.Name, child.Name, true);
}
}
protected override bool IsItemContainer(string path)
{
return true;
}
protected override void GetChildItems(string path, bool recurse)
{
var children = m_Root.GetItemAtPath(path).Children;
foreach (var child in children)
{
WriteItemObject(child.Name, child.Name, true);
}
}
}
/// <summary>
/// This is a node used to represent a folder inside a PowerShell provider
/// </summary>
public class Node
{
private string m_Name;
private List<Node> m_Children;
public string Name { get { return m_Name; } }
public ICollection<Node> Children { get { return m_Children; } }
public Node(string name)
{
m_Name = name;
m_Children = new List<Node>();
}
/// <summary>
/// Adds a node to this node's list of children
/// </summary>
public void AddChild(Node node)
{
m_Children.Add(node);
}
/// <summary>
/// Test whether a string matches a wildcard string ('*' must be at end of wildcardstring)
/// </summary>
private bool WildcardMatch(string basestring, string wildcardstring)
{
//
// If wildcardstring has no *, just do a string comparison
//
if (!wildcardstring.Contains('*'))
{
return String.Equals(basestring, wildcardstring);
}
else
{
//
// If wildcardstring is really just '*', then any name works
//
if (String.Equals(wildcardstring, "*"))
return true;
//
// Given the wildcardstring "abc*", we just need to test if basestring starts with "abc"
//
string leftOfAsterisk = wildcardstring.Split(new char[] { '*' })[0];
return basestring.StartsWith(leftOfAsterisk);
}
}
/// <summary>
/// Recursively check if "child1\child2\child3" exists
/// </summary>
public bool ItemExistsAtPath(string path)
{
//
// If path is self, return self
//
if (String.Equals(path, "")) return true;
//
// If path has no slashes, test if it matches the child name
//
if(!path.Contains(#"\"))
{
//
// See if any children have this name
//
foreach (var child in m_Children)
{
if (WildcardMatch(child.Name, path))
return true;
}
return false;
}
else
{
//
// Split the path
//
string[] pathChunks = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
//
// Take out the first chunk; this is the child we're going to search
//
string nextChild = pathChunks[0];
//
// Combine the rest of the path; this is the path we're going to provide to the child
//
string nextPath = String.Join(#"\", pathChunks.Skip(1).ToArray());
//
// Recurse into child
//
foreach (var child in m_Children)
{
if (String.Equals(child.Name, nextChild))
return child.ItemExistsAtPath(nextPath);
}
return false;
}
}
/// <summary>
/// Recursively fetch "child1\child2\child3"
/// </summary>
public Node GetItemAtPath(string path)
{
//
// If path is self, return self
//
if (String.Equals(path, "")) return this;
//
// If path has no slashes, test if it matches the child name
//
if (!path.Contains(#"\"))
{
//
// See if any children have this name
//
foreach (var child in m_Children)
{
if (WildcardMatch(child.Name, path))
return child;
}
throw new ApplicationException("Child doesn't exist!");
}
else
{
//
// Split the path
//
string[] pathChunks = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
//
// Take out the first chunk; this is the child we're going to search
//
string nextChild = pathChunks[0];
//
// Combine the rest of the path; this is the path we're going to provide to the child
//
string nextPath = String.Join(#"\", pathChunks.Skip(1).ToArray());
//
// Recurse into child
//
foreach (var child in m_Children)
{
if (String.Equals(child.Name, nextChild))
return child.GetItemAtPath(nextPath);
}
throw new ApplicationException("Child doesn't exist!");
}
}
}

Not sure this is a bug, I found this workaround that seems to "do the job". (small update, turns out my original code would "bug out" when working your way down multiple levels.
''' <summary>
''' Joins two strings with a provider specific path separator.
''' </summary>
''' <param name="parent">The parent segment of a path to be joined with the child.</param>
''' <param name="child">The child segment of a path to be joined with the parent.</param>
''' <returns>A string that contains the parent and child segments of the path joined by a path separator.</returns>
''' <remarks></remarks>
Protected Overrides Function MakePath(parent As String, child As String) As String
Trace.WriteLine("::MakePath(parent:=" & parent & ",child:=" & child & ")")
Dim res As String = MyBase.MakePath(parent, child)
Trace.WriteLine("::MakePath(parent:=" & parent & ",child:=" & child & ") " & res)
If parent = "." Then
'res = ".\" & child.Split("\").Last
If String.IsNullOrEmpty(Me.SessionState.Path.CurrentLocation.ProviderPath) Then
res = parent & PATH_SEPARATOR & child
Else
res = parent & PATH_SEPARATOR & child.Substring(Me.SessionState.Path.CurrentLocation.ProviderPath.Length + 1)
'res = parent & PATH_SEPARATOR & child.Replace(Me.SessionState.Path.CurrentLocation.ProviderPath & PATH_SEPARATOR, String.Empty)
End If
Trace.WriteLine("::**** TRANSFORM: " & res)
End If
Return res
End Function

You can work around this if you design you provider so that it expects entering a non-empty Root when you create a new drive. I noticed that the tab-completion mistakenly suggest the complete child path instead of just the child name if the Root property of PSDriveInfo has not been set.
It can be limiting for some providers to always require a non-empty root. The workaround above works well if you don't want to make the users always enter some Root when creating a new drive.

I've listed this as a PowerShell provider bug in Microsoft Connect: Issue with relative path tab-completion (via Get-ChildNames) for NavigationCmdletProvider
If anyone can reproduce this, please visit the link and say so, because Microsoft probably won't look into this if only one person is reporting it.
It looks like this is fixed in PowerShell 3.0. I don't know why Microsoft doesn't want to fix this in older versions, it's not something any code could possibly depend on.

I was able to get it to work with overiding string[] ExpandPath(string path) and setting the ProviderCapabilities.ExpandWildcards capabilities.

Related

Create WinUI3/MVVM Most Recently Used (MRU) List in Menu Bar

I would like to create a classic "Recent Files" list in my Windows app menu bar (similar to Visual Studio's menu bar -> File -> Recent Files -> see recent files list)
The MRU list (List < string > myMRUList...) is known and is not in focus of this question. The problem is how to display and bind/interact with the list according to the MVVM rules.
Microsoft.Toolkit.Uwp.UI.Controls's Menu class will be removed in a future release and they recommend to use MenuBar control from the WinUI. I haven't found any examples, that use WinUI's MenuBar to create a "Recent Files" list.
I'm using Template Studio to create a WinUI 3 app. In the ShellPage.xaml I added
<MenuFlyoutSubItem x:Name="mruFlyout" Text="Recent Files"></MenuFlyoutSubItem>
and in ShellPage.xaml.c
private void Button_Click(object sender, RoutedEventArgs e)
{
mruFlyout.Items.Insert(mruFlyout.Items.Count, new MenuFlyoutItem(){ Text = "C:\\Test1_" + DateTime.Now.ToString("MMMM dd") } );
mruFlyout.Items.Insert(mruFlyout.Items.Count, new MenuFlyoutItem(){ Text = "C:\\Test2_" + DateTime.Now.ToString("MMMM dd") } );
mruFlyout.Items.Insert(mruFlyout.Items.Count, new MenuFlyoutItem(){ Text = "C:\\Test3_" + DateTime.Now.ToString("MMMM dd") } );
}
knowing this is not MVVM, but even this approach does not work properly, because the dynamically generated MenuFlyoutItem can be updated only once by Button_Click() event.
Could anybody give me an example, how to create the "Recent Files" functionality, but any help would be great! Thanks
Unfortunately, it seems that there is no better solution than handling this in code behind since the Items collection is readonly and also doesn't response to changes in the UI Layout.
In addition to that, note that because of https://github.com/microsoft/microsoft-ui-xaml/issues/7797, updating the Items collection does not get reflected until the Flyout has been closed and reopened.
So assuming your ViewModel has an ObservableCollection, I would probably do this:
// 1. Register collection changed
MyViewModel.RecentFiles.CollectionChanged += RecentFilesChanged;
// 2. Handle collection change
private void RecentFilesChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// 3. Create new UI collection
var flyoutItems = list.Select(entry =>
new MenuFlyoutItem()
{
Text = entry.Name
}
);
// 4. Updating your MenuFlyoutItem
mruFlyout.Items.Clear();
flyoutItems.ForEach(entry => mruFlyout.Items.Add(entry));
}
Based on chingucoding's answer I got to the "recent files list" binding working.
For completeness I post the detailed code snippets here (keep in mind, that I'm not an expert):
Again using Template Studio to create a WinUI 3 app.
ShellViewModel.cs
// constructor
public ShellViewModel(INavigationService navigationService, ILocalSettingsService localSettingsService)
{
...
MRUUpdateItems();
}
ShellViewModel_RecentFiles.cs ( <-- partial class )
using System.Collections.ObjectModel;
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
namespace App_MostRecentUsedTest.ViewModels;
public partial class ShellViewModel : ObservableRecipient
{
public ObservableCollection<MRUItem> MRUItems{ get; set;} = new();
// update ObservableCollection<MRUItem>MRUItems from MostRecentlyUsedList
public void MRUUpdateItems()
{
var mruTokenList = StorageApplicationPermissions.MostRecentlyUsedList.Entries.Select(entry => entry.Token).ToList();
var mruMetadataList = StorageApplicationPermissions.MostRecentlyUsedList.Entries.Select(entry => entry.Metadata).ToList(); // contains path as string
MRUItems.Clear(); var i = 0;
foreach (var path in mruMetadataList)
{
MRUItems.Add(new MRUItem() { Path = path, Token = mruTokenList[i++] });
}
}
// called if user selects a recent used file from menu bar list
[RelayCommand]
protected async Task MRULoadFileClicked(int? fileId)
{
if (fileId is not null)
{
var mruItem = MRUItems[(int)fileId];
FileInfo fInfo = new FileInfo(mruItem.Path ?? "");
if (fInfo.Exists)
{
StorageFile? file = await Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(mruItem.Token);
if (file is not null)
{
Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path); // store file.Path into Metadata
MRUUpdateItems();
// LOAD_FILE(file);
}
}
else
{
}
}
await Task.CompletedTask;
}
[RelayCommand]
protected async Task MenuLoadFileClicked()
{
StorageFile? file = await GetFilePathAsync();
if (file is not null)
{
Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path); // store file.Path into Metadata
MRUUpdateItems();
// LOAD_FILE(file);
}
await Task.CompletedTask;
}
// get file path with filePicker
private async Task<StorageFile?> GetFilePathAsync()
{
FileOpenPicker filePicker = new();
filePicker.FileTypeFilter.Add(".txt");
IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
WinRT.Interop.InitializeWithWindow.Initialize(filePicker, hwnd);
return await filePicker.PickSingleFileAsync();
}
public class MRUItem : INotifyPropertyChanged
{
private string? path;
private string? token;
public string? Path
{
get => path;
set
{
path = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(path));
}
}
public string? Token
{
get => token;
set => token = value;
}
public event PropertyChangedEventHandler? PropertyChanged;
}
}
ShellPage.xaml
<MenuBar>
<MenuBarItem x:Name="ShellMenuBarItem_File">
<MenuFlyoutItem x:Uid="ShellMenuItem_File_Load" Command="{x:Bind ViewModel.MenuLoadFileClickedCommand}" />
<MenuFlyoutSubItem x:Name="MRUFlyout" Text="Recent Files..." />
</MenuBarItem>
</MenuBar>
ShellPage.xaml.cs
// constructor
public ShellPage(ShellViewModel viewModel)
{
...
// MRU initialziation
// assign RecentFilesChanged() to CollectionChanged-event
ViewModel.MRUItems.CollectionChanged += RecentFilesChanged;
// Add (and RemoveAt) trigger RecentFilesChanged-event to update MenuFlyoutItems
ViewModel.MRUItems.Add(new MRUItem() { Path = "", Token = ""});
ViewModel.MRUItems.RemoveAt(ViewModel.MRUItems.Count - 1);
}
// MRU Handle collection change
private void RecentFilesChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// project each MRUItems list element into a new UI MenuFlyoutItem flyoutItems list
var i = 0;
var flyoutItems = ViewModel.MRUItems.Select(entry =>
new MenuFlyoutItem()
{
Text = " " + i.ToString() + " " + FilenameHelper.EllipsisString(entry.Path, 65),
Command = ViewModel.MRULoadFileClickedCommand,
CommandParameter = i++
}
);
//// If you want to update the list while it is shown,
//// you will need to create a new FlyoutItem because of
//// https://github.com/microsoft/microsoft-ui-xaml/issues/7797
// Create a new flyout and populate it
var newFlyout = new MenuFlyoutSubItem();
newFlyout.Text = MRUFlyout.Text; // Text="Recent Files...";
// Updating your MenuFlyoutItem
flyoutItems.ToList().ForEach(item => newFlyout.Items.Add(item));
// Get index of old sub item and remove it
var oldIndex = ShellMenuBarItem_File.Items.IndexOf(MRUFlyout);
ShellMenuBarItem_File.Items.Remove(MRUFlyout);
// Insert the new flyout at the correct position
ShellMenuBarItem_File.Items.Insert(oldIndex, newFlyout);
// Assign newFlyout to "old"-MRUFlyout
MRUFlyout = newFlyout;
}

VSCode API TreeView not getting children of elements

In providing a treeview to VSCode, it seems to get all the parent elements but fail to get the children of these parent elements, I've added a debug point into it and saw that it was only ever called with undefined, is there something improper with my implementation?
export class GalleryTreeItem extends vscode.TreeItem {
constructor(
private extensionUri: vscode.Uri,
public collapsibleState: vscode.TreeItemCollapsibleState,
public readonly name: string,
public readonly project?: Project
) {
super(name, collapsibleState);
if (this.project) {
this.contextValue = "gallery";
this.description = `v${this.project.config.userContentVersion}`;
this.tooltip = this.project.config.repositoryUrl;
this.command = {
title: "Plywood Gallery: Open a gallery webview.",
command: "plywood-gallery.Open",
arguments: [this.label],
};
this.iconPath = vscode.Uri.joinPath(
this.extensionUri,
"assets/photo-gallery.png"
);
} else {
this.contextValue = "chapter";
}
}
getChapters() {
if (this.project) {
return Object.keys(this.project.parameters).map(
(name) =>
new GalleryTreeItem(
this.extensionUri,
vscode.TreeItemCollapsibleState.Collapsed,
name
)
);
} else {
return [];
}
}
}
export class InstalledGalleriesExplorerProvider
implements vscode.TreeDataProvider<GalleryTreeItem>
{
constructor(private extensionUri: vscode.Uri) {}
private _onDidChangeTreeData: vscode.EventEmitter<
GalleryTreeItem | undefined | void
> = new vscode.EventEmitter<GalleryTreeItem | undefined | void>();
readonly onDidChangeTreeData: vscode.Event<
GalleryTreeItem | undefined | void
> = this._onDidChangeTreeData.event;
getTreeItem(element: GalleryTreeItem): vscode.TreeItem {
return element;
}
refresh(): void {
this._onDidChangeTreeData.fire();
}
async getChildren(element?: GalleryTreeItem): Promise<GalleryTreeItem[]> {
if (element) {
return Promise.resolve(element.getChapters());
} else {
return getLocalProjects(this.extensionUri).then((projects) =>
projects.map(
(prj) =>
new GalleryTreeItem(
this.extensionUri,
vscode.TreeItemCollapsibleState.None,
prj.config.projectName,
prj
)
)
);
}
}
}
I found the exact passage of interest here: TreeView guide: Tree Data Provider which is a good resource for anyone building TeeViews.
Leaving the collapsibleState as its default of
TreeItemCollapsibleState.None indicates that the tree item has no
children. getChildren will not be called for tree items with a
collapsibleState of TreeItemCollapsibleState.None.
Emphasis added, the default is TreeItemCollapsibleState.None so if you do not explicitly set the state to something else, the children of those nodes will never be retrieved.
As I mentioned in the comments you gave your parent nodes a vscode.TreeItemCollapsibleState.None property. Apparently, vscode is smart enough to not bother looking for children of such nodes - as they can't be opened anyway.
The solution is simply to choose another collapsibleState like TreeItemCollapsibleState.Collapsed or TreeItemCollapsibleState.Expanded.
"If I am seeing it right you are creating GalleryTreeItems which are your parent nodes, but they have vscode.TreeItemCollapsibleState.None which means they act like they are leaf nodes."
Changing it to vscode.TreeItemCollapsibleState.Collapsed fixes it.

How to Create an Input Name Script in Unity [duplicate]

This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 2 years ago.
I have a name input script in the first scene, the plan is I want to call this input in the second scene, when I enter the name in the first scene, then the name will appear in the second scene too, how do you do that?
public class NamaUser : MonoBehaviour {
public InputField nama;
public Text teks;
public void NamaTeks () {
if (nama.text == "") {
teks.text = "Harap Isi Nama";
} else {
teks.text = "Namaku " + nama.text;
}
}
}
You can save the input's value PlayerPrefs.
Set the PlayerPrefs:
//Name of Pref in first parameter
//Value in second parameter
PlayerPrefs.SetString("value", teks.value);
Get the PlayerPref in second scene:
//Name of Pref in first parameter
//Returns value of PlayerPrefs
String a = PlayerPrefs.SetString("value");
Cons:
You can pass data not only between scenes but also between instances (game sessions).
Easy to manage since Unity handles all background process.
Can be used to store data to track highscores.
Pros:
Uses file system.
Data can easily be changed from prefs file.
Or, another way -- use Singelton and DontDestroyOnLoad()
Allows easy access to fields and saves an object between scenes.
For example use this template, to create your class.
using UnityEngine;
public class Singelton<T> : MonoBehaviour where T : Singelton<T>
{
private static T instance = null;
private bool alive = true;
public static T Instance
{
get
{
if (instance != null)
{
return instance;
}
else
{
//Find T
T[] managers = GameObject.FindObjectsOfType<T>();
if (managers != null)
{
if (managers.Length == 1)
{
instance = managers[0];
DontDestroyOnLoad(instance);
return instance;
}
else
{
if (managers.Length > 1)
{
Debug.LogError($"Have more that one {typeof(T).Name} in scene. " +
"But this is Singelton! Check project.");
for (int i = 0; i < managers.Length; ++i)
{
T manager = managers[i];
Destroy(manager.gameObject);
}
}
}
}
//create
GameObject go = new GameObject(typeof(T).Name, typeof(T));
instance = go.GetComponent<T>();
DontDestroyOnLoad(instance.gameObject);
return instance;
}
}
//Can be initialized externally
set
{
instance = value as T;
}
}
/// <summary>
/// Check flag if need work from OnDestroy or OnApplicationExit
/// </summary>
public static bool IsAlive
{
get
{
if (instance == null)
return false;
return instance.alive;
}
}
protected virtual void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this as T;
}
else
{
Debug.LogError($"Have more that one {typeof(T).Name} in scene. " +
"But this is Singelton! Check project.");
Destroy(gameObject);
}
}
protected virtual void OnDestroy() { alive = false; }
protected virtual void OnApplicationQuit() { alive = false; }
}
Example of using:
class MyClass Settings : Singelton<Settings>
{
string param;
}

MDrivenServer - how to expose viewmodel as a REST service

I use standalone MDrivenServer.
Question - is it possible to expose a viewmodel as a REST service in this configuration without MDrivenTurnkey installation, e.g. using url like ...Rest/Get?command=vmname&id=rootobjref ?
Thank you!
No the MDrivenServer has only the persistence mapper api's exposed. In order to simulate a viewmodel driven rest get-service you can go like this in a MVC application:
Derive your controller from ModelDrivenControllerBase<ESType>
/// <summary>
/// command should match a viewmodel, id should be external id for root or $null$
/// will try and match extra params to viewmodel variables
/// will execute actions on root level
/// will return complete vm on success as json except if string attribute RawJSon is found - then this is returned instead
/// </summary>
public virtual ActionResult Get(string command, string id)
{
Eco.ObjectRepresentation.IModifiableVariableList vars = FindAdditionalRequestParamsAndTreatAsVars();
if (string.IsNullOrEmpty(id))
id = ObjectId.nulltoken;
SaveVariablesToSessionState(command, id, vars);
VMClass onlinevm = CreateVMClassFromName(command, id);
if (onlinevm != null)
{
if (!CheckRestAllowed(onlinevm)) <-- turnkey checks the rest allowed flag
return Content("Must set RestAllowed on VM " + command);
foreach (var col in onlinevm.ViewModelClass.Columns)
{
if (col.IsAction)
{
col.ViewModel.ExecuteAction(col.ViewModelClass.RuntimeName, col.RuntimeName);
}
}
return GetJsonFromVm(onlinevm); <-- this can be implemented with the Tajson concept: https://wiki.mdriven.net/index.php/Tajson
}
else
return Content("Access denied");
}
/// <summary>
/// targetViewRootObject may be both string and IEcoObject.
/// If the newrootAsObject happens to be a guid string - and the IClass has a property guid - then we will try and PS-resolve the guid and use it as root
/// </summary>
protected VMClass CreateVMClassFromName(string targetViewName, object targetViewRootObject)
{
EnsureEcoSpace();
if (targetViewRootObject is string)
targetViewRootObject = SafeObjectForId((string)targetViewRootObject);
VMClass createdVMClass = ViewModelHelper.CreateFromViewModel(targetViewName, EcoSpace, targetViewRootObject as IEcoObject, false);
IEcoObject root = targetViewRootObject as IEcoObject;
if (root == null && createdVMClass.ViewModelClass.IClass is IClass && targetViewRootObject is string)
{
// Handle special case of a GUID as target
// Used to act on direct links for an object
if (targetViewRootObject is string)
{
root = EcoSpace.ExternalIds.ObjectForUnkownId(targetViewRootObject as string, createdVMClass.ViewModelClass.IClass as IClass);
if (root != null)
createdVMClass.Content = root.AsIObject();
}
}
bool vm_visibleDueToAccessGroups = createdVMClass.ViewModelClass.ViewModel.VisibleDueToAccessGroups(EcoSpace, root != null ? root.AsIObject() : null);
if (!vm_visibleDueToAccessGroups)
{
return null;
}
LoadVariablesFromSessionStateIfAvailable(targetViewName, SafeIDForObject(targetViewRootObject), createdVMClass.Variables);
return createdVMClass;
}

GWT Cell Tree - leaf nodes

I'm using a cell tree and I have this problem:
I get the data via RPC calls. I decide if a node is a leaf or not - based on the data that I get for its children. For example - if a node has a son called "foo" - then this node should be a leaf.
I don't know how to make this node to be a leaf and not to show its children on the tree. (instead, I want to show them somewhere else, when clicking on the node)
Is it possible? Does anyone have an idea?
Please help me, I'm stuck with it for 2 days...
Thanks!
You can override isLeaf() method to return true or false.
There will be a problem, however, from the UI perspective. Before a user clicks on a node, you don't know if this should be a node or a leaf. This is a little confusing, although I saw such implementations more than once. If your tree is not very large, consider loading all data at once, and then building it the way you want - creating nodes or leafs as necessary.
If each node has a type, could you create some list or map of types that aren't expected to have children in your TreeViewModel impl?
In an impl I did I used a meta-model for all types, but it's not a requirement.
E.g.,
#Override
public boolean isLeaf(Object value) {
boolean result = true;
if (value == null) {
result = false; // assumes all root nodes have children
} else if (value instanceof NavNode) {
final NavNode currentNode = (NavNode) value;
final NodeType currentNodeType = NodeType.fromValue(currentNode.getType());
if (currentNode.hasChildren() || NodeHelper.couldHaveChildren(currentNodeType)) {
result = false;
}
}
return result;
}
// Create a data provider for root nodes
protected ListDataProvider<NavNode> getDataProvider(Collection<NavNode> rootNodes) {
return new ListDataProvider<NavNode>(new LinkedList<NavNode>(rootNodes));
}
// Create a data provider that contains the immediate descendants.
protected AsyncDataProvider<NavNode> getDataProvider(final NavNode node) {
return new AsyncDataProvider<NavNode>() {
#Override
protected void onRangeChanged(final HasData<NavNode> display) {
final Set<NavNode> clientNodes = util.getAncestorNodes(node);
clientNodes.add(node);
final NavigationInfo clientInfo = new NavigationInfo(clientNodes);
navigationService.getNavInfo(clientInfo, node, resources, qualifications, new SafeOperationCallback<NavigationInfo>(eventBus, false) {
#Override
public void onFailureImpl(Throwable caught) {
GWT.log("Something went wrong retreiving children for " + node.getName(), caught);
updateRowCount(0, false);
}
#Override
public void onSuccessImpl(OperationResult<NavigationInfo> or) {
util.mergeNavInfo(or.getResult());
final NavNode nodeFromServer = util.getNode(node.getId());
final Range range = display.getVisibleRange();
final int start = range.getStart();
final Set<NavNode> nodes = util.getNodes(nodeFromServer.getChildren());
updateRowData(display, start, new LinkedList<NavNode>(nodes));
}
});
}
};
}
private static class NodeHelper {
private static final Set<NodeType> PARENTAL_NODE_TYPES;
static {
PARENTAL_NODE_TYPES = new HashSet<NodeType>();
PARENTAL_NODE_TYPES.add(NodeType.ASSET_OWNER);
PARENTAL_NODE_TYPES.add(NodeType.OPERATING_DAY);
PARENTAL_NODE_TYPES.add(NodeType.RESOURCES);
PARENTAL_NODE_TYPES.add(NodeType.RESOURCE);
PARENTAL_NODE_TYPES.add(NodeType.ENERGY);
PARENTAL_NODE_TYPES.add(NodeType.RESERVE);
PARENTAL_NODE_TYPES.add(NodeType.DAY_AHEAD_CLEARED_OFFERS);
PARENTAL_NODE_TYPES.add(NodeType.DRR_LOAD_FORCAST);
PARENTAL_NODE_TYPES.add(NodeType.RESERVE_DISPATCH);
PARENTAL_NODE_TYPES.add(NodeType.RESERVE_RAMP_RATE);
}
public static boolean couldHaveChildren(NodeType nodeType) {
boolean result = false;
if (PARENTAL_NODE_TYPES.contains(nodeType)) {
result = true;
}
return result;
}
}
}