text() returning children text as well - text

http://jsfiddle.net/Lc5gdvge/
In the fiddle above, I've showcased how text(), returns elements text and childrens' as well. How do I avoid this and only make it return "outerdiv"?
NOTE: Just click the blue container to call function.
$(document).ready(function() {
$('.outerdiv').click(function() {
var htmlstring = $(this).text();
alert(htmlstring);
})
});
NOTE 2: This has to be without the use of ID selector.

To get the text of only the .outerDiv and not the children elements, use the following code :
$(this).clone().children().remove().end().text();
What this does is, it clones the element, removes all the children tags, and goes back to the selected tag and gets the text.
The final code should look like :
$(document).ready(function () {
$('.outerdiv').click(function () {
var htmlstring = $(this).clone().children().remove().end().text();
alert(htmlstring);
});
});

Related

Cheerio how to remove DOM elements from selection

I am trying to write a bot to convert a bunch of HTML pages to markdown, in order to import them as Jekyll document. For this, I use puppeteer to get the HTML document, and cheerio to manipulate it.
The source HTML is pretty complex, and polluted with Google ADS tags, external scripts, etc. What I need to do, is to get the HTML content of a predefined selector, and then remove elements that match a predefined set of selectors from it in order to get a plain HTML with just the text and convert it to markdown.
Assume the source html is something like this:
<html>
<head />
<body>
<article class="post">
<h1>Title</h1>
<p>First paragraph.</p>
<script>That for some reason has been put here</script>
<p>Second paragraph.</p>
<ins>Google ADS</ins>
<p>Third paragraph.</p>
<div class="related">A block full of HTML and text</div>
<p>Forth paragraph.</p>
</article>
</body>
</html>
What I want to achieve is something like
<h1>Title</h1>
<p>First paragraph.</p>
<p>Second paragraph.</p>
<p>Third paragraph.</p>
<p>Forth paragraph.</p>
I defined an array of selectors that I want to strip from the source object:
stripFromText: ['.social-share', 'script', '.adv-in', '.postinfo', '.postauthor', '.widget', '.related', 'img', 'p:empty', 'div:empty', 'section:empty', 'ins'],
And wrote the following function:
const getHTMLContent = async ($, selector) => {
let value;
try {
let content = await $(selector);
for (const s of SELECTORS.stripFromText) {
// 1
content = await content.remove(s);
// 2
// await content.remove(s);
// 3
// content = await content.find(s).remove();
// 4
// await content.find(s).remove();
// 5
// const matches = await content.find(s);
// for (m of matches) {
// await m.remove();
// }
};
value = content.html();
} catch(e) {
console.log(`- [!] Unable to get ${selector}`);
}
console.log(value);
return value;
};
Where
$ is the cheerio object containing const $ = await cheerio.load(html);
selector is the dome selector for the container (in the example above it would be .post)
What I am unable to do, is to use cheerio to remove() the objects. I tried all the 5 versions I left commented in the code, but without success. Cheerio's documentation didn't help so far, and I just found this link but the proposed solution did not work for me.
I was wondering if someone more experienced with cheerio could point me in the right direction, or explain me what I am missing here.
I found a classical newby error in my code, I was missing an await before the .remove() call.
The working function now looks like this, and works:
const getHTMLContent = async ($, selector) => {
let value;
try {
let content = await $(selector);
for (const s of SELECTORS.stripFromText) {
console.log(`--- Stripping ${s}`);
await content.find(s).remove();
};
value = await content.html();
} catch(e) {
console.log(`- [!] Unable to get ${selector}`);
}
return value;
};
You can remove the elements with remove:
$('script,ins,div').remove()

Typed.js initialize with existing text and then loop it

I'm working with typed.js to get some words typed. I would like the first word to be showing when the page loads and start the loop from there. In order to get this result I've just placed "nice" in between the span tags, did this the trick.
But... When looking to the following codepen, you can see that the first loop is correct. When the second loop starts, the first word (nice) is not being typed but just appears and disappears quickly. I could really use some help to fix this. Any thoughts?
var typewriter = $('.typewriter');
if(typewriter.length) {
function initTypewriter() {
var typed = new Typed(".typewriter", {
strings: $(".typewriter").attr("data-typewriter").split("|").map(function(e) {
return e
}),
typeSpeed: 80,
backSpeed: 75,
startDelay: 1000,
backDelay: 2000,
loop: !0,
loopcount: false,
showCursor: false,
callback: function(e){ } // call function after typing is done
});
};
initTypewriter();
};
<h2>A <span title="nice, clean, good" class="typewriter" data-typewriter="nice|clean|good">nice</span> example</h2>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.6/typed.min.js"></script>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
CodePen Link
Kind regards
I realise this question is over 2 years old now, but I came across this issue today and couldn't find a solution either so put together a workaround.
Essentially, create 2 instances of Typed JS.
The first removes the existing text and uses the onComplete method to remove itself, clear the text content from the DOM and then setup the second instance to do the actual loop.
My example has no dependencies outside of Typed JS, but you could adapt to jQuery selectors, etc, pretty easily.
Demo here: https://codepen.io/jneale/pen/pogyzXK
HTML
<h1>Hello <span class="typed-replaced">world</span></h1>
Javascript
function setupTypedReplace() {
// the text node to type in
var typed_class = 'typed-replaced';
// the original text content to replace, but also use
var replace_text = 'world';
var options = {
strings: ['there', 'buddy', replace_text], // existing text goes at the end
typeSpeed: 80,
backSpeed: 60,
backDelay: 1000,
loop: true,
smartBackspace: false,
cursorChar: '_',
attr: null
};
// clear out the existing text gracefully then setup the loop
new Typed('.' + typed_class, {
strings: [replace_text, ''],
backSpeed: options.backSpeed,
backDelay: options.backDelay,
cursorChar: options.cursorChar,
attr: options.attr,
startDelay: 700,
onComplete: function (t) {
// existing text has now been removed so let's actually clear everything out
// and setup the proper Typed loop we want. If we don't do this, the original
// text content breaks the flow of the loop.
t.destroy();
document.getElementsByClassName(typed_class)[0].textContent = '';
new Typed('.' + typed_class, options);
}
});
}
setupTypedReplace();

Getting element through attribute value in javaScript

I want to get the element through javascript based on attribute value, as per the example I want to get element through attribute "doc-cid" and the value of "doc-cid" is dynamic.
<p align="center" style="font-family:times;" doc-cid="11303">
<font size="2" doc-cid="11304">55</font></p>
<p id="demo">Click the button to change the text of a list item.</p>
<button onclick="myFunction()">Try it</button>
<script>
function myFunction()
{
var list = document.getElementsByTagName("doc-cid=\"11303\"")
alert(list.getAttribute("doc-cid"));
}
</script>
I'll start with a few pointers about your above code, and follow through with a solution.
POINTERS
doc-cid is not a "TagName", it is a custom attribute. hence, your function trying to getElementsByTagName will always fail for "doc-cid".
doc-cid can by dynamic (or not) it doesn't matter. A lookup function will always get the CURRENT DOM value of your element (unless you specifically make it do otherwise).
I suggest you use the new "data-*" attribute in html, it keeps your markup valid (if that is important to you). The use would be as follows:
your content
SOLUTION
function getElementByAttribute (attribute, value, start) {
var start = start || document,
cache, //store found element
i;
function lookup(start) {
// if element has been found and cached, stop
if (cache) {
return;
} else if (start.getAttribute(attribute) === value) { // check if current element is the one we're looking for
cache = start;
return;
} else if (start.childElementCount > 0) { // if the current element has children, loop through those
lookup(start.children[0]);
} else if (start.nextElementSibling) { // if the current element has a sibling, check it
lookup(start.nextElementSibling);
} else {
return;
}
}
lookup(start);
return cache;
}
You simply give the function the attribute name you are looking up, the value you need to match and the starting point of the lookup (if no starting point is specified it'll start at the very beginning of your page (much slower).
Below is an example for your markup:
// you have no easy to get starting point, so we'll traverse the DOM
getElementByAttribute('doc-cid', '11303');
If you want to start at a better node, you can add a wrapper div element and give it id="wrapper" then you could call the function as follows:
var start = document.getElementById('wrapper');
getElementByAttribute('doc-cid', '11303', start);
Hope this helps.

Unable to load Meteor template, set context and bind events

I am trying to render and append a template (ticket_edit) to the body. I need to set a context to the newly appended template, and the events of ticket_edit should be bound to that template.
The code:
Template.ticket.events = {
'click a.edit' : function (event) {
//when the edit button has been clicked, load template 'ticket_edit'
//with the current context. Please see situation 1 and 2.
}
}
Template.ticket_edit.events = {
'click a.save' : function (event) {
//this won't do anything when i have supplied a context!
}
}
So the problem is:
-I can set the context, but then the events are not bound to the newly added template.
-If I don't set the context the events are bound properly.
But i need both the events and the context.
Situation 1:
'click a.edit' : function (event) {
//applying a context to the template will result in events not being bound.
$('body').append(Meteor.render(Template.ticket_edit(this)));
}
Sitation 2:
'click a.edit' : function (event) {
//this way, the events will execute properly and i can save my ticket.
//However, no context is supplied!
$('body').append(Meteor.render(Template.ticket_edit));
}
Does anybody have a good method for doing this? I'm fairly new to Meteor, so maybe you have a better method of dynamically loading templates.
Don't use jQuery for this, just do it directly with the template and a #with block. Something like this:
<body>
{{> tickets}}
</body>
<template name="tickets">
{{#each tickets}}
{{> ticket}}
{{/each}}
{{#with currentTicket}}
{{> editTicket}}
{{/with}}
</template>
Template.tickets.tickets = function() {
return Tickets.find();
};
Template.tickets.currentTicket = function () {
return Tickets.findOne({ _id: Session.get( "currentTicket" ) });
};
Template.ticket.events({
'click a.edit' : function () {
Session.set( "currentTicket", this._id );
}
});
Because we're using a #with block, the editTicket template won't get rendered until you click the edit button (setting the "currentTicket" in the Session).
It's also possible to just do this (no #with block):
{{> editTicket currentTicket}}
But that causes editTicket to always be rendered, just without any context until the Session var gets set.
Note that because we're using Session, the user won't be interrupted by reloads/hot code pushes.

Does Knockout.mapping make ALL nested objects observable?

I am trying to map all possible nested objects of a JSON object so that each and every one is becomes an observable. I was under the impression that the use of ko.mapping.fromJS would result in all objects and their objects becoming observable. However, I am not seeing that happen.
If you look at the JSFiddle and code below you will see that the span initially displays the value "Test". My intention is for the button click to update the viewModel with the contents of stuff2, which should change the span's value to "Test2". However, the button click does not update anything.
http://jsfiddle.net/Eves/L5sgW/38/
HTML:
<p> <span>Name:</span>
<span data-bind="text: IntroData.Name"></span>
<button id="update" data-bind="click: Update">Update!</button>
</p>
JS:
var ViewModel = function (data) {
var me = this;
ko.mapping.fromJS(data, {}, me);
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};
return me;
};
var stuff = {
IntroData: {
Name: 'Test'
}
};
var stuff2 = {
IntroData: {
Name: 'Test2'
}
};
window.viewModel = ko.mapping.fromJS(new ViewModel(stuff));
ko.applyBindings(window.viewModel);
Is it just that I have to make use of mapping options to have the nested objects be made observable? If so, what if the JSON object is so vast and complex (this one obviously isn't)? Can some recursive functionality be used to loop through each object's nested objects to make them all observable?
Modifying the Update function as below will work.
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};

Resources