Dynamic Kendo Menu which uses Ajax to call actions - kendo-menu

Here is code I have and the menu displays.
#(Html.Kendo().Menu()
.Name("MainMenu")
.BindTo(_userMenu.UserMenuItems, mappings =>
{
mappings.For<UserMenuItemModel>(binding => binding.ItemDataBound((item, UserMenuItemModel) =>
{
item.Text = UserMenuItemModel.Description;
item.ActionName = UserMenuItemModel.ActionName;
item.ControllerName = UserMenuItemModel.ControllerName;
item.HtmlAttributes = (new { data-ajax = "true", });
})
.Children(UserMenuItemModel => UserMenuItemModel.SubMenuItems));
mappings.For<UserMenuItemModel>(binding => binding.ItemDataBound((item, UserMenuItemModel) =>
{
item.Text = UserMenuItemModel.Description;
item.ActionName = UserMenuItemModel.ActionName;
item.ControllerName = UserMenuItemModel.ControllerName;
}));
})
)
However, clicking an item makes a non ajax call which won't work in our architecture.
Basically, with the kendo menu item I'm shooting to have links which function exactly like what this does.
<li>
#Ajax.ActionLink(item.Description, item.ActionName, item.ControllerName, ajaxMainMenuOptions)
</li>
I thought I was going to be able to set the HTMLAttributes for each item but that's read only.
Our dynamic menu is hiearchical with no limits on levels. I'm working another angle with a recursive partial page, but having a lot of trouble with CSS.
The kendo menu was extremely simple to bind to a our self referencing model, I just have to figure out how to get it to make an Ajax.ActionLink call.
Any pointers are greatly appreciated.
Thanks.

Just realized the Attributes have an add.
This appears to be what we were shooting for.
#(Html.Kendo().Menu()
.Name("MainMenu")
.BindTo(_userMenu.UserMenuItems, mappings =>
{
mappings.For<UserMenuItemModel>(binding => binding.ItemDataBound((item, UserMenuItemModel) =>
{
item.Text = UserMenuItemModel.Description;
item.ActionName = UserMenuItemModel.ActionName;
item.ControllerName = UserMenuItemModel.ControllerName;
item.LinkHtmlAttributes.Add("data-ajax", "true");
item.LinkHtmlAttributes.Add("data-ajax-method", "GET");
item.LinkHtmlAttributes.Add("data-ajax-mode", "replace");
item.LinkHtmlAttributes.Add("data-ajax-update", "#MainBody");
})
.Children(UserMenuItemModel => UserMenuItemModel.SubMenuItems));
mappings.For<UserMenuItemModel>(binding => binding.ItemDataBound((item, UserMenuItemModel) =>
{
item.Text = UserMenuItemModel.Description;
item.ActionName = UserMenuItemModel.ActionName;
item.ControllerName = UserMenuItemModel.ControllerName;
item.LinkHtmlAttributes.Add("data-ajax", "true");
item.LinkHtmlAttributes.Add("data-ajax-method", "GET");
item.LinkHtmlAttributes.Add("data-ajax-mode", "replace");
item.LinkHtmlAttributes.Add("data-ajax-update", "#MainBody");
}));
})
)
For anybody else, this is a fully dynamic menu built using a hierarchical, self-referencing model. UserMenuItemModel contains a List
Adding keywords because I really never found anything like this, Kendo Menu Ajax
ActionLink hierarchical self-referencing
Hope this helps others.

Related

How to check if element exists & keep track of it's mutations once found using mutationObserver

First & foremost excuse my ignorance as im still trying to wrap my head aroud mutationObservers
I'm creating a chrome extension that detects certain words from an ordered list. when content script runs at first the ordered list element isn't rendered so I used a mutationObserver to capture it when it's created however as I scroll through the page the ordered list keeps increasing in size via API calls, how can I keep track of the new results rendered under the ordered list?
What i've done so far
const waitForElm = async (selector) => {
return new Promise((resolve) => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver((mutations) => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
});
};
waitForElm("ol.artdeco-list").then((elm) => {
console.log("Element is ready");
// Tried to create a new mutationObserver here.
});
I've tried creating another mutationObserver once promise was resolved on the same element that was found "ol.artdeco-list", but that didn't seem to work...
Any advice??

Allow new value on QuickPick in a VSCode extension

I want to display to the user an input with some options, but the user can answer a new one.
Using showQuickPick I can show some options, but if the user answer a different option the return is undefined.
It's possible to do what I want?
I have already think about create a New option and then show an InputBox to the user, but I don't want that the user need to answer two questions.
I have used a solution where I inject the user input in the list of items.
It works pretty well:
const choices = ['a', 'b']
async function getUserSelectdValue() {
return new Promise((resolve) => {
const quickPick = window.createQuickPick();
quickPick.items = choices.map(choice => ({ label: choice }));
quickPick.title = 'Choose your favorite value:'
quickPick.onDidChangeValue(() => {
// INJECT user values into proposed values
if (!choices.includes(quickPick.value)) quickPick.items = [quickPick.value, ...choices].map(label => ({ label }))
})
quickPick.onDidAccept(() => {
const selection = quickPick.activeItems[0]
resolve(selection.label)
quickPick.hide()
})
quickPick.show();
})
}
Please let me know if you need further explanations
It sounds like you might be interested in the approach taken in this issue?
https://github.com/microsoft/vscode/issues/89601

Editing an entity's decorated text

We have a Figure decorator which allows us to insert a link which you can hover over to get a preview of an image. We insert this image along with some metadata (caption, etc.) using a modal form. This all works great. However we also want the ability to click the link and pop up the modal to edit it.
Entity.replaceData() works great for updating the metadata, the only problem that remains is the decorated text which comes from the modal too. It appears the Entity knows little to nothing about the content it's decorating.
How can we find and replace the text? Is there a way around this?
(I've tried setting the content in Draft to an arbitrary single character and making the decorator show the content/title (which would be fine), however when trying to delete the figure, Draft seems to jump over the content and delete something before it. I guess it's due to different text lengths. I thought setting it as 'IMMUTABLE' would solve this but that didn't help.)
EDIT:
Here's my decorator:
function LinkedImageDecorator(props: Props) {
Entity.mergeData(props.entityKey, { caption: "hello world" });
const entity = Entity.get(props.entityKey);
const data = entity.getData();
return <LinkedImage data={data} text={props.children} offsetKey={props.offsetKey} />
}
function findLinkedImageEntities(contentBlock: ContentBlock, callback: EntityRangeCallback) {
contentBlock.findEntityRanges((character) => {
const entityKey = character.getEntity();
return (
entityKey != null &&
Entity.get(entityKey).getType() === ENTITY_TYPE.IMAGE
);
}, callback);
}
export default {
strategy: findLinkedImageEntities,
component: LinkedImageDecorator,
editable: false,
};
As you can see, I'm testing out Entity.mergeData which will eventually be the callback of my LinkedImage component (which would open a modal onClick.) So the metadata is easy to update, I just need to be able to update the decorated text which is passed in as props.children.
So I finally solved this with the help of Jiang YD and tobiasandersen. Here goes...
First I inject my decorator with a reference to my editor (which keeps track of EditorState):
const decorators = new CompositeDecorator([{
strategy: findLinkedImageEntities,
component: LinkedImageDecorator,
props: { editor: this }
}];
this.editorState = EditorState.set(this.editorState, { decorator });
From there I can do this in my LinkedImageDecorator:
const { decoratedText, children, offsetKey, entityKey, editor } = this.props;
// This looks messy but seems to work fine
const { startOffset, blockKey } = children['0'].props;
const selectionState = SelectionState.createEmpty(blockKey).merge({
anchorOffset: startOffset,
focusOffset: startOffset + decoratedText.length,
});
const editorState = editor.getEditorState();
let newState = Modifier.replaceText(
editorState.getCurrentContent(),
selectionState,
"my new text",
null,
entityKey,
);
editor.editorState = EditorState.push(editorState, newState, 'insert-fragment');
Not sure if this is the cleanest way of doing this but it seems to work well!
just put the global component instance reference in the decorator props.
const compositeDecorator = new CompositeDecorator([
{
strategy: handleStrategy,
component: HandleSpan,
props: {parent:refToYourComponentWhichContainsTheEditorState}
}
props.parent.state.editorState.getCurrentContent()

No results when setting the firstpage header

When you look at the documentation for office-js api's you have three header locations:
primary
firstpage
even
I would expect that setting a firstpage header would only show on the first page. However I doesn't seem to do anything. Am I missing something? The documentation is not very clear on this. In this sample only the primary is showing up.
private setHeader(): void {
Word.run((context) => {
let body = context.document.body;
let sections = context.document.sections;
context.load(sections, 'body/style');
return context.sync()
.then(() => {
let primary = sections.items[0].getHeader(Word.HeaderFooterType.primary);
primary.insertText('primary', Word.InsertLocation.replace)
let firstPage = sections.items[0].getHeader(Word.HeaderFooterType.firstPage);
firstPage.insertText('first page', Word.InsertLocation.replace);
context.sync()
.then(() => { },
((result: OfficeErrorMessage) => {
this.setErrorState(result);
})
);
;
});
});
}
ps. the sample is typescript.
very good question. if you set the headers with the API and then select the 'Different First Page' option on the Design ribbon of the header
you will see that effectively the header is there. The bad part is that we are fixing a bug to make sure its shown, basically we will expose those properties in the section object, we are planning to ship those as part of the next deliverable of the Word js API. Now, those options are actually on the file format (the odd and events on the settings package as <w:evenAndOddHeaders/> , and the 'Different first' a <w:titlePg> element is added on the section definition, so maybe inserting an OOXML can be a workaround for now.
Hope this helps!

YUI AutoComplete Example Problem

I was hunting for an implementations of YUI AutoComplete and I came across this script from the site asklaila.com -
<script type="text/JavaScript">
YAHOO.example.ACJson = new function() {
this.oACDS = new YAHOO.widget.DS_XHR("/AutoComplete.do",
["Suggestions[0].Results","Name"]);
this.oACDS.queryMatchContains = true;
this.oACDS.scriptQueryAppend = "city=Mysore"; // Needed for YWS
function fnCallback(e, args) {
document.searchForm.where.focus();
acSelected = true;
return false;
}
this.oAutoComp = new YAHOO.widget.AutoComplete('what','whatContainer', this.oACDS);
this.oAutoComp.itemSelectEvent.subscribe(fnCallback);
this.oAutoComp.formatResult = function (oResultItem,sQuery) {
return oResultItem[0];
}
this.oAutoComp.queryDelay = 0;
this.oAutoComp.useIFrame = true;
this.oAutoComp.prehighlightClassName = "yui-ac-prehighlight";
this.oAutoComp.minQueryLength = 2;
this.oAutoComp.autoHighlight = false;
this.oAutoComp.textboxFocusEvent.subscribe(function() {
this.oAutoComp.sendQuery("");
});
this.oAutoComp.doBeforeExpandContainer = function(oTextbox, oContainer, sQuery, aResults) {
var pos = YAHOO.util.Dom.getXY(oTextbox);
pos[1] += YAHOO.util.Dom.get(oTextbox).offsetHeight + 2;
YAHOO.util.Dom.setXY(oContainer,pos);
return true;
};
}
</script>
It is implenting the YUI AutoComplete Dropdown. What I want to understand is what this
this.oACDS = new YAHOO.widget.DS_XHR("/AutoComplete.do", ["Suggestions[0].Results","Name"]);
does and its effects on code.
That's using an older version of YUI, but it is setting up a DataSource for the autocomplete to read from. This particular DataSource uses XHR to request information from the server to populate the autocomplete field.
"Autocomplete.do"
Is a relative URL that is being queried by the DataSource every time the autocomplete fires while the user is typing.
["Suggestions[0].Results","Name"]
Is the responseSchema that tells the DataSource how to parse the results from the request to the URL. It needs to know how to parse the data so that it can show the proper results.
this.oACDS = new YAHOO.widget.DS_XHR("/AutoComplete.do", ["Suggestions[0].Results","Name"]);
On every key press, it fetches a json response from the server, and uses it to populate the autocomplete dropdown. The json contains names to display only at this node, "Suggestions[0].Results" in the "name" field.
If you have any trouble, ask ahead. I wrote that piece of code for asklaila.com
I was hunting for implementations of
YUI Autocomplete and I came across
this script...
Why not take a look at YUI AutoComplete page for in-depth examples.
Yahoo! UI Library: AutoComplete