I've searched thru Corvid docs and Stack, not finding anything.
Is there a way to appendChild() in Wix Corvid(Code)?
EDIT: Wix does not allow DOM access directly. I assumed that people answering this would know i was looking for an alternative to appencChild and knew this method could not be used as is in Wix.
so to clarify: is there a way to add a child to a parent element using Wix's APIs?
It depends what you are trying to achieve,
the only thing off the top of my head is adding more items to a repeater
which you can do by first getting the initial data from the repeater, adding another item to array and reassign the data property of the repeater
const initialData = $w('#repeater').data
const newItem = {
_id: 'newItem1', // Must have an _id property
content: 'some content'
}
const newData = [...initialData, newItem]
$w('#repeater').data = newData
https://www.wix.com/corvid/reference/$w.Repeater.html#data
In Corvid, you cannot use any function which accesses the DOM.
Coming from one of the developers of Corvid:
Accessing document elements such as div, span, button, etc is off-limits. The way to access elements on the page is only through $w. One small exception is the $w.HtmlComponent (which is based on an iFrame). This element was designed to contain vanilla HTML and it works just fine. You just can't try to trick it by using parent, window, top, etc.
Javascript files can be added to your site's Public folder, but the same limitations apply - no access to the DOM.
Read more here: https://www.wix.com/corvid/forum/main/comment/5afd2dd4f89ea1001300319e
Related
I'm hard stuck with this one so any advice welcome!
Ive been trying to create a flow that goes to this website https://dlv.tnl-uk-uni-guide.gcpp.io/ and scrapes the data from each table in the Subject Areas drop down list. My knowledge of HTML is sketchy at best but from what I understand it's a dynamic html table that just refreshes with new data rather than going to a new url. I can extract the subject list as a variable and in my head i think i just need to add this to a UI selector action but despite numerous attempts i've got absolutely nowhere. Anyone got any ideas as to how i could fix this or work around?
Because it is not a conventional drop-down using the "Set drop-down list value on web page" doesn't work all that well.
You can use a bit of javascript and variables for this.
Hit F12 to show developer tools, you will see there is a list of hidden items with the class class="gug-select-items gug-select-hide" you will use this in the javascript.
Then add a 'Press button on web page' function and add the 'drop-down' element, which is a <div>
Then edit the element selector and change it to text editor.
then change the selector to make use of the nth-child(0) selector but use a variable for the index.
so it looks something like #gug-overall-ranking-select > div.gug-select-items > div:nth-child(%ddIdx%)
Use the "Run JavaScript function on web page" function to get the number of options available to the drop-down. (child elements)
The returned result is text, so convert it to a number that can be used in the loop.
function ExecuteScript() { /*your code here, return something (optionally); */
var firstDDlist = document.querySelector("#gug-overall-ranking-select > div.gug-select-items.gug-select-hide");
return firstDDlist.children.length;
}
In the loop each element will be pressed and cause the table to reload.
The table data extraction can then also be done in the loop, but that this code only shows the looping through the options.
The full flow 'code' (copy this and paste it in power automate).
WebAutomation.LaunchEdge.LaunchEdge Url: $'''https://dlv.tnl-uk-uni-guide.gcpp.io/?taxonomyId=36&/#gug-university-table''' WindowState: WebAutomation.BrowserWindowState.Normal ClearCache: False ClearCookies: False WaitForPageToLoadTimeout: 60 Timeout: 60 BrowserInstance=> Browser
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { /*your code here, return something (optionally); */
var firstDDlist = document.querySelector(\"#gug-overall-ranking-select > div.gug-select-items.gug-select-hide\");
return firstDDlist.children.length;
}''' Result=> numberOfItems
Text.ToNumber Text: numberOfItems Number=> itemCount
LOOP ddIdx FROM 1 TO itemCount STEP 1
WebAutomation.PressButton.PressButton BrowserInstance: Browser Control: appmask['Web Page \'h ... sity-table\'']['Div \'gug-select-selected\''] WaitForPageToLoadTimeout: 60
END
It should end up looking like this:
Flow running:
With using Power Automate Desktop (PAD), the goal is to be a low-code solution. Of course knowing HTML is a bonus and will help you on tricky webpages or problems, but not knowing much is alright usually. I'm not really comfortable going to that web page you shared but you could try the below option.
PAD has a built in function in the action pane:
'Browser automation' > 'Web data extraction' > 'Extract data from web page'
Try using that and when asked to add UI Element select the table/dropdown list to see what information you get back. If that doesn't work you might need to try out JavaScript or another method.
In my project there is one usage case: user click one button and then copy some data to clipboard for next step.
The copied data is related to the clicked button, and is stored in the component state.
I do some search, and find the potential solution as following:
function copyToClipboard(text){
var dummy = document.createElement("input");
document.body.appendChild(dummy);
dummy.setAttribute('value', text);
dummy.select();
document.execCommand("copy");
document.body.removeChild(dummy);
}
to some extend, we need to create a dummy element, set the copied data to the dummy element and select the element, then execute the execCommand(copy) method.
is it possible to do this without creating dummy element? I know there are some react plugin about clipboard, but I just want to use vanilla javascript. thank you
Your solution works well.
If the value you want to copy is not yet rendered on the DOM, your Document.createElement('input')... method is a good way to create a document node that Document knows about, but that is not visible to the user. Once you use .createElement() you can then call execCommand() on it to copy the value to the clipboard.
The execCommand() method is exposed by HTML5's Document. This means Document has to know about the node you are targeting before you can use the method (this is called Selection).
However, if you want to copy text from an element already rendered on the dom (e.g an input in a form), you could use React's callback ref. Here's a good example of using ref to do this. It's pretty simple, so using a library is likely to be overkill.
I have an view that extends the current project view, where we add multiple tabs (notebook pages) to show information from other parts of a project.
One of these pages is an overview page that summarizes what is under the other tabs, and I'd like to link the headlines for each section directly to each displayed page. I've currently solved this by using the index of each tab and calling bootstrap's .tab('show') method on the link within the tab:
$(".overview-link").click(function (e) {
e.preventDefault();
var sel = '.nav-tabs a:eq(' + $(this).data('tab-index') + ')';
$(sel).tab('show');
});
This works since I've attached a data-tab-index="<int>" to each header link in my widget code, but it's brittle - if someone adds a tab later, the current indices will be broken. Earlier I relied on the anchor on each tab, but that broke as well (and would probably break if a new notebook page were inserted as well).
Triggering a web client redirect / form link directly works, but I want to show a specific page in the view:
this.do_action({
type: 'ir.actions.act_window',
res_model: 'my.model.name',
res_id: 'my.object.id',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'current'
});
Is there any way to link / redirect the web client directly to a specific notebook page tab through the do_action method or similar on FormWidget?
If I understood well you want to select the tab from the JavaScript (jQuery) FormWidget taking into account that the id could change if anybody install another module that adds another tab
Solution 0
You can add a class to the page in the xml form view. You can use the id of the element selected by this class name in order to call the right anchor and select the right tab item. This should happen when the page is completely loaded:
<page class="nb_page_to_select">
$('a[href=#' + $('.nb_page_to_select').attr('id') + ']').click()
NOTE: As you have said the following paragrah I assume that you know where to run this instruction. The solution I suggest is independent of the index.
This works since I've attached a data-tab-index="<int>" to each
header link in my widget code, but it's brittle - if someone adds a
tab later, the current indices will be broken. Earlier I relied on the
anchor on each tab, but that broke as well (and would probably break
if a new notebook page were inserted as well).
Solution 1
When the page is loaded you can get the tab list DOM object like this:
var tablist = $('ul[role="tablist"]')
And then you can click on the specifict tab, selecing by the text inside the anchor. So you don't depend on the tab index:
tablist.find('a:contains("Other Information")').click()
I think if you have two tabs with the same text does not make any sense, so this should be sufficient.
Solution 2
Even if you want to be more specific you can add a class to the notebook to make sure you are in the correct notebook
<notebook class="nt_to_change">
Now you can use one of this expressions in order to select the tab list
var tablist = $('div.nt_to_change ul.nav-tabs[role="tablist"]')
// or
var tablist = $('div.nt_to_change ul[role="tablist"]')
Solution 3
If the contains selector doesn't convince you because it should be equal you can do this as well to compare and filter
tablist.find('a').filter(function() {
return $.trim($(this).text()) === "Other Information";
}).click();
Where "Other Information" is the string of the notebook page
I didn't tried the solution I'm giving to you, but if it doesn't work at least may be it makes you come up with some idea.
There's a parameter for XML elements named autofocus (for buttons and fields is default_focus and takes 1 or 0 as value). If you add autofocus="autofocus" to a page in XML, this page will be the displayed one when you open the view.
So, you can try to add this through JavaScript, when the user clicks on the respective link -which honestly, I don't know how to achieve that by now-. But you can add a distinctive context parameter to each link in XML, for example context="{'page_to_display': 'page x'}". When you click on the link, I hope these context keys will arrive to your JS method.
If not, you can also modify the fields_view_get method (here I wrote how to do that: Odoo - Hide button for specific user) to check if you get the context you've added to your links and add the autofocus parameter to the respective page.
As you said:
This works since I've attached a data-tab-index="" to each header
link in my widget code, but it's brittle - if someone adds a tab
later, the current indices will be broken.
I assume that your app allow multi-user interaction in realtime, so you have to integrate somewhere in your code, an update part function.
This function will trig if something has changed and cleanout the data to rebuilt the index in order to avoid that the current indices will be broken.
Per CKEditor, initialize widget added with insertElement, we are doing an insertElement() and then initializing with initOn(). The problem is that some of the elements we are inserting are not supposed to be widgets and initOn() makes them widgets and the context menu doesn't work right. I am having trouble finding any properties inside the item/element to tell if something is/is not a widget so I can then call initOn().
Cross-posted downstream on Drupal.org here https://www.drupal.org/node/2466297
First of all - which element do you mean?
(Note: In this section I am assuming that a widget was correctly and fully initialised.)
Widget element
A widget can obviously consists of many elements. One of them is called the "widget element" and this is the element which you "upcasted" and which you can later access through widget.element.
Since CKEditor 4.5.0 there will be such method available:
Widget.isDomWidgetElement = function( node ) {
return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-widget' );
};
You can of course already use this code to check if a given node is a widget element.
Widget wrapper
Second important element is the widget's wrapper. It is created during data processing if a widget element was marked to be upcasted or when initOn() is called if the widget element wasn't wrapped yet. You can access this element through the widget.wrapper property.
Since CKEditor 4.5.0 there will be a following method available:
Widget.isDomWidgetWrapper = function( node ) {
return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-wrapper' );
};
And again - you can use this code already.
Important note here - since you mention insertElemet() in your question. As I explained in CKEditor, initialize widget added with insertElement editor#insertElement() does not trigger data processing. Therefore, element that you insert is inserted as is. This means that the widget wrapper is not created during insertion and will be created once you call initOn().
Finding widgets by any element
Many times you want to find a widget instance by some element that you have (any element that can be inside a widget). There's a useful method for that: getByElement().
What should become a widget? Aka - how to deal with editor.insertElement()?
You mentioned that you use editor.insertElement() and that you don't know which elements are supposed to be widgets. This should never happen. editor.insertElement() is a quite low level method which will not do all the data processing and upcasting magic which editor.insertHtml() does. It means that it is supposed to be used in a different case - when you want to insert exactly the element that you have.
For instance, your table plugin is building a table structure to be inserted into editor. You know that the table is empty, so you control every bit of it (other plugins should not interfere here). It is also important that it's the table's plugin decision, not e.g. a template's plugin decision. The table's plugin control the table feature, while the template plugin only uses tables. So in such case, when you have a full control, you can use editor.insertElement(). Then you always know what you insert and what is supposed to become a widget.
In all other scenarios you should use editor.insertHtml(), so the whole data processing layer is triggered. Thanks to it other features like the widgets system, the link plugin (which turns empty anchors into fake objects), etc. can prepare the data that you insert to be fully editable and integrated.
Tl;dr
If your plugin knows what it does, it can use editor.insertElement(), but since it knows what it does it will know which inserted element must become a widget.
If your plugin does not fully control the situation, then you should use the editor.isertHtml() method which is far more automated and will turn proper elements into widgets based on the upcast callbacks.
I have an issue about ajax.
make an example: I have a menu list (ul li) and by default the first one has class 'current'.
if I click the second item assign class 'current' to him with jquery and remove it from the previous one.
Now, I need reload the menu list with ajax, so I call the ajax function that calls a php function that return an update html list.
But in this way I lose the 'current' class from the second list, that before I assigned with jquery.
Happens to me many times to have this type of problems.. What is the correct solution to solve it?
one way of doing it is to get the index of the li with the class current and in the ajax success callback assign the current class to the appropriate index
look at this fiddle to get the index of the li http://jsfiddle.net/3nigma/zyayj/
in your success callback
success:function(data){
var i = index -1; // index is zero based and eq() is 1 based
$("ul li:eq(i)").addClass("current");
}
I think your question indicates that you don't already know that web pages are "stateless", meaning that they do not "automatically" hold or store anything that you do with them.
Please see my answer ho a privious question which summarizes the ways to deal with retaining the state of things - the principles would remain the same for your menus.
Stateless HTML