Algolia autocomplete doesn't go to the selected URL with static sources - autocomplete

ANSWER GetItemURL is only used for keyboard interactions. It is not used for the mouse handling. Mouse handling is done by the rendered HTML (from the template). The simplest approach is to use a a HREF In your template, or an OnClick handler that takes you to another page!
—————————-
I've copied the default autocomplete code for static sources, the completion and filters work, and getItemURL is called correctly, however on click the URL does not change.
I've created a sandbox you can see ithere.
Default code:
const { autocomplete } = window["#algolia/autocomplete-js"];
const autocomplete_id = "#autocomplete-search-box";
function CreateAutoComplete(appid, search_api_key, index_name) {
console.log("CreateAutoComplete Called");
autocomplete({
container: autocomplete_id,
placeholder: "Type T to get completions",
getSources() {
return [
{
sourceId: "links",
getItems({ query }) {
const items = [
{ label: "Twitter", url: "https://twitter.com" },
{ label: "GitHub", url: "https://github.com" }
];
return items.filter(({ label }) =>
label.toLowerCase().includes(query.toLowerCase())
);
},
getItemUrl({ item }) {
console.log("GetItemURL", item);
console.log("returning", item.url);
return item.url;
},
templates: {
item({ item }) {
return item.label;
}
}
}
];
}
});
}

Remember that getItemUrl() is expecting a keyboard interaction (navigate via arrows and click enter) to navigate over to the result URL, not a click.
This is working in your codesandbox, although the redirect is being blocked by Twitter/Github.

Related

Hiding DOM elements with a chrome extension without causing a flicker

Preface:
I am aware that there is a duplicate question out there. I am posting it again because it has no answers (and it's from 4 years ago).
General description of what I want:
I want to be able to hide a DOM-element (adding Element.style.display = "none") before the DOM is loaded into the view.
What I've tried:
Other posts point to using a MutationObserver and running it on the document element.
To ensure that we are able to hide an element before the DOM is loaded, we are to run the script containing the MutationObserver as a content_script with "run_at":"document_start".
I did all of this, and I still see a flicker (the elements appear when I load a page and then quickly disappear).
What I'm trying to do:
There's a ul which contains some li with some text on the page I inject my content_script.js into. I populate my popup.html with <text, checkbox> pairs. If the checkbox is checked, the li containing said text is visible, else it is hidden. I want it to persist between refreshes, hence the use of storage.
Things work - but there's a flicker whenever I refresh the page. The elements are there, then they're gone. I don't want them to show up in the first place!
My code:
When I detect that the DOM elements I may remove have loaded, I generate an Object that indicates whether I should hide or keep visible that specific DOM element.
I then set its Element.style.display to none or block accordingly.
/**manifest.json
...
"content_scripts": [
{
"matches": [
"some_website_url"
],
"js": [
"content_script.js"
],
"run_at": "document_start"
}
]
...
*/
///content_script.js
const mutationObserver = new MutationObserver((mutations) => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.tagName) {
if (node.querySelector(potentially_hidden_element_selector)) {
chrome.storage.sync.get("courses", ({ courses }) => {
chrome.storage.sync.set({ "courses": generateCourseList(courses) }, () => {
const courseElements = Array.from(node.closest('ul').querySelectorAll('a[data-parent-key="mycourses"]'))
courseElements.forEach(courseElement => {
const courseName = getCourseName(courseElement)
const isVisible = courses[courseName]
updateCourseElementInSidebar(courseElement, isVisible)
})
})
})
// We found what we were looking for so stop searching
mutationObserver.disconnect()
}
}
}
}
})
mutationObserver.observe(document, { childList: true, subtree: true })
EDIT 1:
My generateCourseList method depends on the DOM elements I may try to hide - so I can't call the chrome.storage.set method before the DOM has loaded I think.
When I refresh the page, a list of courses eventually populates the DOM.
I then populate the storage's courses object based on these course elements' innerText properties. I set each of these elements' visibility to true or false based on one of two factors: if this course is already defined in the courses object, keep its visibility status, if it isn't, set it to true (visible by default).
I can't make certain DOM elements visible/hidden if I don't have reference to them though. So if I try to call generateCourseList before those specific DOM elements have loaded, I end up trying to retrieve all the course elements (document.querySelectorAll('a[data-parent-key="mycourses"]')) and get returned nothing. I end up setting courses in chrome.storage to nothing because of this chrome.storage.sync.set({ "courses": generateCourseList(courses) }....
EDIT 2:
Here is all of my code. I try to chrome.storage.sync.get as soon as I can, and I try to not depend on the result of chrome.storage.sync.set.
I try to delete the elements as soon as I can, but I'm having difficulty doing so. This is because I have difficulty knowing when the content I want to access (the course elements) have fully loaded. Previously, I was detecting when one course element was visible, and when it was, I assumed all were. This was a mistake. I was able to access the one courselement the moment it popped up, but sometimes only 4 of the 6 course elements were actually loaded. I can't hardcode this number, because it changes from person to person. I can't just tackle them one by one, because then I wouldn't know when to disconnect the MutationObserver. I used the debugger and tried to find what element is loaded soon after all 6 course elements are loaded, and that is the header#page-header.row element. I still get a flicker, though less noticeable than before.
Anything I can do to make it even less noticeable?
function start_mutation_observer() {
chrome.storage.sync.get({ 'savedCourses': {} }, ({ savedCourses }) => {
const observer = new MutationObserver((mutations) => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
// The page header gets updated AFTER the courseList is updated - so once it's in the page, we know the courseElements are too
if (document.querySelector('header#page-header.row')) {
observer.disconnect()
const generatedCourses = generateCourseList(savedCourses)
const courseElements = getCourseElements()
// Set visibility of course elements
courseElements.forEach(courseElement => {
const courseName = getCourseElementTextContent(courseElement);
const isShown = generatedCourses[courseName];
setCourseElementVisibility(courseElement, isShown);
});
chrome.storage.sync.set({ 'savedCourses': generatedCourses });
return
}
}
}
});
observer.observe(document, { childList: true, subtree: true });
// In case the content script has been injected when some of the DOM has already loaded
onMutation([{ addedNodes: [document.documentElement] }]);
});
}
function getCourseElements() {
const COURSE_ELEMENT_SELECTOR = 'ul > li > a[data-parent-key="mycourses"]'
return Array.from(document.querySelectorAll(COURSE_ELEMENT_SELECTOR))
}
function getCourseElementTextContent(courseElement) {
const COURSE_ELEMENT_TEXT_CONTAINER_SELECTOR = 'a[data-parent-key="mycourses"] > div > div > span.media-body'
return courseElement.querySelector(COURSE_ELEMENT_TEXT_CONTAINER_SELECTOR).textContent
}
function generateCourseList(savedCourses) {
// Turns [[a, b], [b,c]] into {a:b, b:c}
return Object.fromEntries(getCourseElements().map(courseElement => {
const courseName = getCourseElementTextContent(courseElement)
const isShown = savedCourses[courseName] ?? true
return [courseName, isShown]
}))
}
function setCourseElementVisibility(courseElement, isShown) {
if (isShown) {
courseElement.style.display = "block"
} else {
courseElement.style.display = "none"
}
}
start_mutation_observer()
EDIT 3:
I think it's as good as can be now. I only refresh the visibility of the course elements that were just loaded into the DOM. There's essentially no flicker now (there is a slight one, but its' the same amount of flickering without my extension).
Here is the code for the MutationObserver
function start_mutation_observer() {
let handledCourseElements = new Set()
chrome.storage.sync.get({ 'savedCourses': {} }, ({ savedCourses }) => {
const observer = new MutationObserver((mutations) => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
const courseElements = getCourseElements()
const courseElementsAdded = courseElements.length > handledCourseElements.size
// If a courseElement was added, update visibility of those that weren't already processed
if (courseElementsAdded) {
const generatedCourses = generateCourseList(savedCourses)
courseElements
.filter(courseElement => !handledCourseElements.has(courseElement))
.forEach(courseElement => {
const courseName = getCourseElementTextContent(courseElement)
const courseShouldBeVisible = generatedCourses[courseName];
setCourseElementVisibility(courseElement, courseShouldBeVisible);
handledCourseElements.add(courseElement)
})
}
// The page header gets updated AFTER the courseList is updated - so once it's in the page, we know the courseElements are too
if (document.querySelector('header#page-header.row')) {
observer.disconnect()
chrome.storage.sync.set({ 'savedCourses': generateCourseList(savedCourses) });
return
}
}
}
});
observer.observe(document, { childList: true, subtree: true });
// In case the content script has been injected when some of the DOM has already loaded
onMutation([{ addedNodes: [document.documentElement] }]);
});
}
Reading storage is slow and asynchronous, so you need to do it at the beginning:
chrome.storage.sync.get('courses', ({ courses }) => {
chrome.storage.sync.set({ 'courses': generateCourseList(courses) });
const observer = new MutationObserver(onMutation);
observer.observe(document, { childList: true, subtree: true });
onMutation([{addedNodes: [document.documentElement]}]);
function onMutation(mutations) {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.tagName && node.querySelector(potentially_hidden_element_selector)) {
observer.disconnect();
processNode(node, courses);
}
}
}
}
});
function processNode(node, courses) {
const courseElements = Array.from(
node.closest('ul').querySelectorAll('a[data-parent-key="mycourses"]'));
courseElements.forEach(courseElement => {
const courseName = getCourseName(courseElement);
const isVisible = courses[courseName];
updateCourseElementInSidebar(courseElement, isVisible);
});
}

How add menu button to ag-Grid row?

I'm using ag-Grid Enterprise Vue.
I see in the docs how to enable a "context menu" that is available by right-clicking any individual cell.
I instead would love to have a special column (pinned to the right) that has a button (maybe looking like ⚙ or ...) that opens a menu upon left-click.
How could I go about enabling this? I have found no examples in the docs.
Ag-grid Cell containing menu button is a similar question but has no answer.
From this comment on this ag-grid-enterprise issue, I was able to fork the example, and I think it will work for my situation.
The relevant code is:
var gridOptions = {
columnDefs: columnDefs,
enableRangeSelection: true,
getContextMenuItems: getContextMenuItems,
allowContextMenuWithControlKey: true,
onCellClicked: params => {
console.log(params);
if(params.column.colDef.field === '...'){
params.api.contextMenuFactory.showMenu(params.node, params.column, params.value, params.event)
}
},
onCellContextMenu: params => {
params.api.contextMenuFactory.hideActiveMenu()
}
};
function getContextMenuItems(params) {
console.log('getContextMenuItems', params);
const node = params.node;
console.log('node.id, node.rowIndex, node.data', node.id, node.rowIndex, node.data);
var result = [
{
name: `Alert 'Row ${node.rowIndex + 1}'`,
action: function() {
window.alert(`Row ${node.rowIndex + 1}`);
},
cssClasses: ['redFont', 'bold']
},
'separator',
{
name: 'Checked',
checked: true,
action: function() {
console.log('Checked Selected');
},
icon: '<img src="../images/skills/mac.png"/>'
},
'copy' // built in copy item
];
return result;
}

add leave or stay on the page alert in ionic 3?

I'm trying to add a popup that will be shown on a page when the user starts to populate data in the form and then he decided to go somewhere else in the app.
This popup will show this message: 'Do you want to leave this page and save your changes?'
three buttons are available: Stay, Leave and Save before leaving.
I'm new to ionic logic and I couldn't figure out how to do this.
I started by adding a button in the page that shows the popup (Still don't know how to trigger the event when the user clicks on any link of the sidebar for example). when the user clicks on that button the popup is shown with the three buttons.
The problem is that I don't know how to implement the handlers of these buttons.
This is what I have in the ts file :
leaveOrStayModal() {
let e = event || window.event;
e.stopPropagation();
this.alertMixin.presentAlert(
'Do you want to leave this site?\n',
"You haven't saved your changes!",
'Stay',
'Leave',
'Save',
null,
() => {
console.log('leave handler')
// this.navCtrl.push() Here I don't know how to get the exact link clicked from the sidebar ? to go to
},
() => {
console.log('Save handler')
//here I want to save the form ?
}
)
}
the popup code:
presentAlert(title: string, message: string, btnOneText: string, btnTowText: string, btnThreeText: string,
btnOneHandler?: () => void, btnTowHandler?: () => void, btnThreeHandler?: () => void,
present: boolean = true) {
let confirm = this.alertCtrl.create({
title: title,
message: message,
buttons: [
{
text: btnOneText,
handler: () => {
if (btnOneHandler) {
btnOneHandler();
}
}
},
{
text: btnTowText,
handler: () => {
if (btnTowHandler) {
btnTowHandler();
}
}
},
{
text: btnThreeText,
handler: () => {
if (btnThreeHandler) {
btnThreeHandler();
}
}
}
]
});
if (present) {
confirm.present().then();
}
return confirm;
}
And this is the button that shows the popup (to be removed )
<button ion-button icon-left item-right type="button" (click)="leaveOrStayModal()"> Click to show modal </button>
You should leverage life cycle hook ionViewCanLeave for that. Some basic documentation here: https://ionicframework.com/docs/api/navigation/NavController/
For your context I just drafted the way I would do it (its a bit dirty):
userCanLeave = false;
ionViewCanLeave() {
// here you can use other vars to see if there are reasons we want to keep user in this page:
if (!this.userCanLeave) {
return new Promise((resolve, reject) => {
let alert = this.alertCtrl.create({
title: 'Are you sure?',
message: 'The form data may be lost',
buttons: [
{
text: 'Stay',
role: 'cancel',
handler: () => {
console.log('User stayed');
this.userCanLeave = false;
reject();
}
},
{
text: 'Leave',
handler: () => {
console.log('User leaves');
this.userCanLeave = true;
resolve();
}
},
{
text: 'Save',
handler: () => {
console.log('User saved data');
// do saving logic
this.userCanLeave = true;
resolve();
}
}
]
});
alert.present();
});
} else { return true }
}
userCanLeave - here is just example of a var that defines if the page has the state where we would not want a user to leave "freely".
then we use promise to ensure that user can not leave without answering dialogue options, we wait for their answers to define whether life cycle hook gets true/false flag to proceed.
Please note also that this life cycle hook only "kicks in" when a view (page) gets off the stack (pops) if you would push in a new view - it won't guard that. But in this case new pushed in page won't destroy user's data in the form anyway and user can safely return to it once you dismiss that newly pushed in page.
Hope this helps.

kendo-ui autocomplete extend

I'm trying to extend the kendo-ui autocomplete control: I want the search start when te user hit enter, so basically I've to check the user input on keydown event.
I've tried to catch the keydown event with this code:
(function($) {
ui = kendo.ui,
Widget = ui.Widget
var ClienteText = ui.AutoComplete.extend({
init: function(element,options) {
var that=this;
ui.AutoComplete.fn.init.call(this, element, options);
$(this).bind('keydown',function(e){ console.log(1,e); });
$(element).bind('keydown',function(e){ console.log(2,e); });
},
options: {
[...list of my options...]
},
_keydown: function(e) {
console.log(3,e);
kendo.ui.AutoComplete.fn._keydown(e);
}
});
ui.plugin(ClienteText);
})(jQuery);
None of the binded events gets called, only the _keydown, and then I'm doing something wrong and cannot call the autocomplete "normal" keydown event.
I've seen a lot of examples that extend the base widget and then create a composite widget, but I'm not interested in doing that, I only want to add a functionality to an existing widget.
Can someone show me what I'm doing wrong?
Thank you!
What about avoiding the extend and take advantage of build in options and methods on the existing control : http://jsfiddle.net/vojtiik/Vttyq/1/
//create AutoComplete UI component
var complete = $("#countries").kendoAutoComplete({
dataSource: data,
filter: "startswith",
placeholder: "Select country...",
separator: ", ",
minLength: 50 // this is to be longer than your longest char
}).data("kendoAutoComplete");
$("#countries").keypress(function (e) {
if (e.which == 13) {
complete.options.minLength = 1; // allow search
complete.search($("#countries").val());
complete.options.minLength = 50; // stop the search again
}
});
This code actually work:
(function($) {
ui = kendo.ui,
ClienteText = ui.AutoComplete.extend({
init: function(element,options) {
ui.AutoComplete.fn.init.call(this, element, options);
$(element).bind('keydown',function(e){
var kcontrol=$(this).data('kendoClienteText');
if (e.which === 13) {
kcontrol.setDataSource(datasource_clientes);
kcontrol.search($(this).val());
} else {
kcontrol.setDataSource(null);
}
});
},
options: {
name: 'ClienteText',
}
});
ui.plugin(ClienteText);
})(jQuery);
but I don't know if it's the correct way to do it.

Kendo UI DragAndDrop TreeView item to a ListView

I have a requirement to enable drag and drop from a kendo-ui tree view to a templated list view.
I've tried the following:
1.Enabling dragAndDrop on the treeview and configuring the listview as a kendoDropTarget
2.Disabling dragAndDrop on the treeview and instead configuring that control as kendoDraggable to the listview configured as a kendoDropTarget
<div>
<div id="treeview">
</div></div>
<div id="favorites-window" style="height:185px;width:1170px">
<div class="report-reader" style="height:185px;width:1170px;overflow:auto">
<div id="listView"></div>
</div>
</div>
$("#favorites-window").kendoWindow({
width: "1180",
height: "185",
resizable: false,
draggable: false,
actions: ["Custom"],
title: "Favorites"
});
$("#listView").kendoListView({
selectable: "single",
navigatable: false
}).kendoDropTarget({
drop: function (e) {
console.log(e);
var item = getObjects(nucleusTreeJsonData, 'text', e.draggable.hint.text());
$("#listView").data("kendoListView").dataSource.add(item);
}
});
var inlineDefault = new kendo.data.HierarchicalDataSource({
data: [
{ text: "Furniture", items: [
{ text: "Tables & Chairs" },
{ text: "Sofas" },
{ text: "Occasional Furniture" }
] },
{ text: "Decor", items: [
{ text: "Bed Linen" },
{ text: "Curtains & Blinds" },
{ text: "Carpets" }
] }
]
});
$("#treeview").kendoTreeView({
dragAndDrop: true,
dataSource: inlineDefault,
dataTextField: "text"
});
//.kendoDraggable({
// container: $("#tree-pane"),
// hint: function () {
// return $("#treeview").clone();
// },
// dragstart: draggableOnDragStart
//});
$("#treeview").data("kendoTreeView").bind("dragstart", function (e) {
if ($(e.sourceNode).parentsUntil(".k-treeview", ".k-item").length == 0) {
e.preventDefault();
}
});
/*$("#treeview").data("kendoTreeView").bind("drop", function (e) {
e.preventDefault();
var copy = this.dataItem(e.sourceNode).toJSON();
if (e.dropPosition == "over") {
//var item = getObjects(nucleusTreeJsonData, 'text', e.sourceNode.textContent);
$("#listView").data("kendoListView").add(copy);
}
});*/
$('ul.k-group.k-treeview-lines div').children().css('font-weight', 'bold').find('div').css('font-weight', 'normal');
I'm not having much luck with it. Please take a look at my fiddle. Any suggestions would be greatly appreciated
http://jsfiddle.net/OhenewaDotNet/JQBZN/16/
I know this is an old question but I had it, too, so I went ahead and figured it out using this fiddle.
http://jsfiddle.net/JQBZN/74/
This is really really basic and is probably architected awfully but I think it at least demonstrates the key point(s):
$("#treeview").kendoTreeView({
dragAndDrop: true,
dataSource: inlineDefault,
dataTextField: "text",
drag: function (e) {
/* Manually set the status class. */
if (!$("#treeview").data('kendoTreeView').dataItem(e.sourceNode).hasChildren && $.contains($('#favorites-window')[0], e.dropTarget)) {
e.setStatusClass('k-add');
} else {
e.setStatusClass('k-denied');
}
},
drop: function (e) {
if (e.valid) {
/* Do your adding here or do it in a drop function elsewhere if you want the receiver to dictate. */
}
e.preventDefault();
}
});
If the KendoUI tool set isn't doing what you want it to do, you may find it easier to do what you want to do with jQuery UI. They're both implementing the same jQuery core library.
If you go with jQuery UI, it's simply a matter of binding 'draggable' to the element you want to drag, and 'droppable' to your targets. From there, you can wire up handlers to do pretty much anything you want.
I've set up a simple jsFiddle that demonstrates how this would work:
http://jsfiddle.net/e2fZk/23/
The jQuery code is really simple:
$(".draggable").draggable();
$(".container").droppable({
drop: function (event, ui) {
var $target = $(this);
var $source = ui.draggable;
var newUrl = $source.find("input").val();
alert("dropped on " + $target.attr("id") + ", setting URL to " + newUrl);
$target.find("#imageDiv").html("<img id='myImage' />")
.find("#myImage").attr("src", newUrl);
}
});
The API documentation is here:
Draggable
Droppable