I'm having a unknown number of elements like so:
<div class="item item-1"></div>
<div class="item item-2"></div>
<div class="item item-3"></div>
What I want to do, is check if each item has a classname starting with "item-". If true, then extract the id. Something like this:
$("container").each(function
if ($(this).hasClassNameStartingWith("item-"))
console.debug(theId);
);
How is this possible?
Thanks in advance!
Use the contains selector on the class attribute:
$('container[class*=" item-"]').each( function() {
var classID = null;
var classes = $(this).attr('class').split( ' ' );
for (var i = 0, len < classes.length; i < len; ++i) {
var class = classes[i];
if (class.match( /^item-/ )) {
classID = class.replace("item-",'');
break;
}
}
if (classID) {
... do something ...
}
});
Note the use of quotes to include the space, given your sample mark up. You could also omit the space if the "item-N" class could appear at the beginning of the list and you were sure that there weren't any classes that would accidentally match that string.
Updated example to show how to extract identifier portion of class name.
You can perform a regular expression match on the child elements' class attribute:
var itemIDRe = /(?:^|[ \t\n\r\f\u200b]+)item-([^ \t\n\r\f\u200b]+)/;
$('#container').children().each(function() {
var match = itemIDRe.exec($(this).attr('class'));
var itemID = match ? match[1] : null;
// Do something with itemID.
});
The regular expression is based on the HTML 4 definition for the class attribute and white space.
Related
My typo3 version is 11.5.10.
I have two footer images, one for German and one for English .
I want to render different image for different language.
I tried this way for German language.
[siteLanguage("locale") == "de_DE"]
50 = COA
50 {
wrap = <div class="footer__item col-sm-6 col-md-3 img-custom">|</div>
stdWrap {
typolink {
parameter = {$myconstant.footer-logo-link-4}
parameter.noTrimWrap = || _blank|
}
}
50 = IMAGE
50 {
file = user_uploads/german-footer.png
layoutKey = srcset
layout.srcset {
element = <img WIDTH###" SOURCECOLLECTION###" ###PARAMS### ###ALTPARAMS### ###SELFCLOSINGTAGSLASH###> }
}
}
[end]
And for English language.
[siteLanguage("locale") == "en_US"]
50 = COA
50 {
wrap = <div class="footer__item col-sm-6 col-md-3 img-custom">|</div>
stdWrap {
typolink {
parameter = {$myconstant.footer-logo-link-5}
parameter.noTrimWrap = || _blank|
}
}
50 = IMAGE
50 {
file = user_uploads/english-footer.png
layoutKey = srcset
layout.srcset {
element = <img WIDTH###" SOURCECOLLECTION###" ###PARAMS### ###ALTPARAMS### ###SELFCLOSINGTAGSLASH###> }
}
}
[end]
In both language FE i get English-footer.
I also tried Different function of siteLanguage like
siteLanguage("navigationTitle")
siteLanguage("locale")
siteLanguage("hreflang") etc..
What i am doing wrong ?
Thanks in advance!
I get the following error in Typoscript Object Browser
Errors and warnings
Warning : Line 5696: Object Name String, "[siteLanguage" contains invalid character "[". Must be alphanumeric or one of: "_:-/." Show details
Warning : Line 5718: Object Name String, "[END]" contains invalid character "[". Must be alphanumeric or one of: "_:-/." Show details
Warning : Line 5719: Object Name String, "[siteLanguage" contains invalid character "[". Must be alphanumeric or one of: "_:-/." Show details
Warning : Line 5741: Object Name String, "[END]" contains invalid character "[". Must be alphanumeric or one of: "_:-/." Show details
In general, you should avoid conditions whenever possible. Conditions are evil!
Conditions are checked for each and every page request - before any cached content is touched. extensive use of conditions will be a performance killer.
For more details, search for "typo3 condition performance"
Using TypoScript if
A better way would be using the if-function, which is cachable, in combination with getText siteLanguage.
page.10 = TEXT
page.10.data = siteLanguage:languageId
page.10.stdWrap.wrap = <p>siteLanguage:languageId=|</p>
page.20 = IMAGE
page.20 {
if.value = 1
if.equals.data = siteLanguage:languageId
file = EXT:example/Resources/Public/typo3_package_de.png
}
page.30 = IMAGE
page.30 {
if.value = 0
if.equals.data = siteLanguage:languageId
file = EXT:example/Resources/Public/typo3_package_en.png
}
I can use tags in regular page fields without any issue. When using tags within blocks (within a streamfield), the UI works and the tags are saved BUT the current page tags do not show up when loading the page in the admin. That's because the current value is not in the template anymore, it's in a JSON loaded via telepath.
I can confirm that the tags are saved and present in the data passed to initBlockWidget in the page source but these are ignored. Also, if I used a regular text field instead of the tag-widget, I can see the saved-values in the admin.
This is the code I have (which used to be enough before the refactor with telepath).
from wagtail.admin.widgets import AdminTagWidget
class TagBlock(TextBlock):
#cached_property
def field(self):
field_kwargs = {"widget": AdminTagWidget()}
field_kwargs.update(self.field_options)
return forms.CharField(**field_kwargs)
I think the following link is what I need to complete somehow to get it to work: https://docs.wagtail.io/en/stable/reference/streamfield/widget_api.html#form-widget-client-side-api
I've tried with this:
class AdminTagWidgetAdapter(WidgetAdapter):
class Media:
js = [
"wagtailadmin/js/vendor/tag-it.js",
"js/admin/admin-tag-widget-adapter.js",
]
register(AdminTagWidgetAdapter(), AdminTagWidget)
And under js/admin/admin-tag-widget-adapter.js:
console.log("adapter"); // this shows up in the console
class BoundWidget { // copied from wagtail source code
constructor(element, name, idForLabel, initialState) {
var selector = ':input[name="' + name + '"]';
this.input = element.find(selector).addBack(selector); // find, including element itself
this.idForLabel = idForLabel;
this.setState(initialState);
}
getValue() {
return this.input.val();
}
getState() {
return this.input.val();
}
setState(state) {
this.input.val(state);
}
getTextLabel(opts) {
const val = this.getValue();
if (typeof val !== 'string') return null;
const maxLength = opts && opts.maxLength;
if (maxLength && val.length > maxLength) {
return val.substring(0, maxLength - 1) + '…';
}
return val;
}
focus() {
this.input.focus();
}
}
// my code here:
class AdminTagWidget {
constructor(html, idPattern) {
this.html = html;
this.idPattern = idPattern;
}
boundWidgetClass = BoundWidget;
render(placeholder, name, id, initialState) {
console.log("RENDER", placeholder, name, id, initialState); // this does not show
var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id);
var idForLabel = this.idPattern.replace(/__ID__/g, id);
var dom = $(html);
$(placeholder).replaceWith(dom);
// eslint-disable-next-line new-cap
return new this.boundWidgetClass(dom, name, idForLabel, initialState);
}
}
console.log("here") // does show in the console
// variants I've tried:
//window.telepath.register('wagtail.admin.widgets.tags.AdminTagWidget', AdminTagWidget);
//window.telepath.register('wagtail.widgets.AdminTagWidget', AdminTagWidget);
window.telepath.register('path.where.its.used.AdminTagWidget', AdminTagWidget)
The log from my custom render method does not show. It seems that I'm not calling the right path within window.telepath.register but I don't know how what the string is supposed to be...
I'm not even sure if this is the right way forward.
Notes:
it works in regular field, the question is about tags in blocks
I'm using Wagtail version 2.13.2 but I've also tried with 2.15 without any difference.
In the console, I can log window.telepath and see my custom widget. It's just not "applied" to anything
Your WidgetAdapter class needs a js_constructor attribute:
class AdminTagWidgetAdapter(WidgetAdapter):
js_constructor = 'myapp.widgets.AdminTagWidget'
class Media:
js = [
"wagtailadmin/js/vendor/tag-it.js",
"js/admin/admin-tag-widget-adapter.js",
]
Any string value will work here - it just needs to uniquely identify the class, so it's recommended to use a dotted module-like path to avoid colliding with others. This then matches the string you pass to window.telepath.register on the Javascript side:
window.telepath.register('myapp.widgets.AdminTagWidget', AdminTagWidget)
I am tring to get a string from a list after it has been sorted using jquery ui sortable
Using a simple list ( without sorting)
<ul id = "description">
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
and applying
$('#description').text().replace(/(\r\n|\n|\r)/gm," ");
I get: one two three
However if I make the list sortable and move items around, and using
function getOrder() {
alert($("#description").html());//gives a correct html ul
alert($("#description").text());// no spaces between words
var ans = $('#description').text().replace(/(\r\n|\n|\r)/gm," ");
alert("You got " + ans)
};
The output changes to three twoone or threetwoone - spacing messed up. $("description").html()
shows that the html is correct ( style="" gets added to the li tag for some reason) , but $('#description').text().replace(/(\r\n|\n|\r)/gm," "); does not give a properly spaced string.
Why does sorting change the behaviour and what can I do to resolve this? Appreciate any response please
To get the text, I would create a toString function:
function toString(element) {
var children = element.children;
var str = '';
for (var i = 0; i < children.length; i++) {
str += (!i ? '' : ' ') + children[i].textContent;
}
return str;
}
And then call that function with the HTMLElement (not jQuery object) of the list passed in as an argument.
This solution does not utilize jQuery, so you can use it with SortableJS or jQuery Sortable.
$.map( $('li'), function (element) { return $(element).text() }).join(' ');
works to get the list items into a properly spaced string
I have a working web app that reads local .txt files and displays the content in a div element. I create a span element out of each word because I need to be able to select any word in the document and create an EEI (Essential Elements of Information) from the text. I then register a click handler on the containing div and let the event bubble up. The three functions below show reading the file, and parsing it, and populating the text div with spans:
function readInputFile(evt) {
reset();
var theFile = evt.target.files[0];
if(theFile) {
$("#theDoc").empty(); //Clean up any old docs loaded
var myReader = new FileReader();
var ta = document.getElementById("theDoc");
myReader.onload = function(e) {
parseTheDoc(e.target.result);
initialMarkup();
};
myReader.readAsText(theFile);
} else {
alert("Can not read input file: readInputFile()");
}
}
function parseTheDoc(docContents) {
var lines = docContents.split("\n");
var sentWords =[];
for(var i = 0; i < lines.length; i++) {
sentWords = lines[i].split(" ");
words = words.concat(sentWords);
words.push("<br>");
}
//examineWords(words);
createSpans(words);
}
function createSpans() {
for (var i = 0; i < words.length; i++) {
var currentWord = words[i];
if(currentWord !== "<br>") {
var $mySpan = $("<span />");
$mySpan.text(currentWord + " ");
$mySpan.attr("id", "word_" + i);
$("#theDoc").append($mySpan);
buildDocVector(currentWord, i, $mySpan);
}
else {
var $myBreak = $("<br>");
$myBreak.attr("id", "word_" + i);
$("#theDoc").append($myBreak);
buildDocVector("br", i, $myBreak);
}
}
//console.log("CreateSpans: Debug");
}
So basically a simple fileReader, split on \n, then tokenize on white space. I then create a span for each word, and a br element for each \n. It's not beautiful, but it satisfies the requirement, and works. My question is, is there a more efficient way of doing this? It just seems expensive to create all these spans, but my requirement is to annotate the doc and map any selected word to a data model/ontology. I can't think of a way to allow the user to select any word, or combination of words (control click) and then perform operations on them. This works, but with large docs (100 pages) I start having performance/memory issues. I understand this is more a design question and may not be appropriate, but I'd really like to know if there are more performant solutions.
Im trying to modify this script
http://www.dynamicdrive.com/dynamicindex16/formremember2.htm
to work for textareas, and not just input text boxes. Heres what im guessing are the relevant parts of the script, i just cant figure it out myself
rememberForm.prototype.savevalues=function(){ //get form values and store in cookie
for (var i=0; i<this.fields.length; i++){
if (this.fields[i].type=="text")
this.cookiestr+=this.fields[i].fname+":"+escape(this.fields[i].value)+"#"
}
if (typeof this.togglebox!="undefined"){ //if "remember values checkbox" is defined
this.persistdays=(this.togglebox.checked)? this.persistdays : -1 //decide whether to save form values
this.cookiestr=(this.togglebox.checked)? this.cookiestr+"toggleboxid:on;" : this.cookiestr
}
else //if checkbox isn't defined, just remove final "#" from cookie string
this.cookiestr=this.cookiestr.substr(0, this.cookiestr.length-1)+";"
setCookie(this.cookiename, this.cookiestr, this.persistdays)
}
rememberForm.prototype.recallvalues=function(){ //populate form with saved values
var cookievalue=getCookie(this.cookiename)
if (cookievalue!=""){ //parse cookie, where cookie looks like: field1:value1#field2:value2...
var cookievaluepair=cookievalue.split("#")
for (var i=0; i<cookievaluepair.length; i++){
if (cookievaluepair[i].split(":")[0]!="toggleboxid" && this.getfield(cookievaluepair[i].split(":")[0]).type=="text")
this.getfield(cookievaluepair[i].split(":") [0]).value=unescape(cookievaluepair[i].split(":")[1])
else //else if name in name/value pair is "toggleboxid"
this.togglebox.checked=true
}
}
The method persistfields(id, ...) sets the fields you want to persist in the cookie. The fields are looked up by id so I guess adding a textarea with an id attribute would suffice.
For example:
<form id="myFormId">
<input type="text" id="someInputId" />
<textarea id="textareaId"></textarea>
</form>
<script>
var f = new rememberForm('myFormId');
f.persistfields('someInputId', 'textareaId');
</script>
This will add the input and textarea to the rememberForm instance fields property.
UPDATE
The problem lies in this method of rememberForm. I formatted the code for readability since the original source has horrible formatting.
rememberForm.prototype.savevalues = function() {
for (var i = 0; i < this.fields.length; i++) {
// PROBLEM: only allows type="text"
if (this.fields[i].type == "text") {
this.cookiestr += this.fields[i].fname + " : " + escape(this.fields[i].value) + "#"
}
if (typeof this.togglebox != "undefined") {
this.persistdays = (this.togglebox.checked) ? this.persistdays : -1;
this.cookiestr = (this.togglebox.checked) ? this.cookiestr + "toggleboxid:on;" : this.cookiestr
} else {
this.cookiestr = this.cookiestr.substr(0, this.cookiestr.length - 1) + ";"
setCookie(this.cookiename, this.cookiestr, this.persistdays)
}
}
}
As mentioned in my comment it will test the type of the input element to be 'text'. If you'd like to add textareas in the cookie you could change that line to:
if (this.fields[i].type == "text" || this.fields[i].type == 'textarea') {
That should work.