I am working on my own javascript library. The library has a getAttr() function that operates like this:tex(selector).getAttr(name); Here is the library's code:
(function(){
var tex = function(s){
return new tex.fn.init(s);
};
tex.fn = tex.prototype ={
init : function(s){
if(!s){
return this;
}
else{
this.length = 1;
if (typeof s === "object"){
this[0] = s;
}
else if(typeof s === "string"){
var obj;
obj = document.querySelector(s);
this[0] = obj;
}
return this;
}
}
}
tex.fn.init.prototype = tex.fn;
tex.fn.init.prototype = {
attr : function(name, value){
/*if(name && !value){
return this[0].getAttribute(name);
}else if(name && value){
this[0].setAttribute(name,value);
};*/
this[0].setAttribute(name,value);
},
getAttr : function(name){
return this[0].getAttribute(name);
},
removeAttr : function(name){
this[0].removeAttribute(name);
},
print : function(txt){
this[0].innerHTML = txt;
}
};
window.TechX = tex;
})();
Here is the code in the body section:
<nav>
<ul>
<li><a id="MainDropper">Pick your method:</a>
<ul>
<li><a>book</a>
<ul>
<li><a data-val="Book" href="javascript:setType(this)">Book</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
And here is my code in the head section:
function setType(obj){
tex("#MainDropper").print(tex(obj).getAttr("data-val"));
}
So when I click the book link the mail dropper should have the text "Book." But instead I get an error in the library that says, "the [object global] has no method getAttrribute." Does anyone know how I can fix this problem?
Thank you so much.
The credit should really go to Bergi, but in the interest of providing an answer, here goes.
When you use a listener like:
<a onclick="code" ... >
then the content of the onclick attribute is effectively wrapped in a function that is called with the element as this, like:
function listener(){code}
listener.call(element);
However, when the href attribute and javascript pseudo–protocol (aka "javascript: URL scheme") are used the code is executed as global code, so more or less just:
code;
HTML5 says to behave as if a new script element was inserted with the code as content. So within the listener function (execution context), this will reference the global (window) object.
Note that browser behaviour can differ here (e.g. variables declared in javascript URLs) as there was no specification for how such code should be treated until HTML5, which really just tries to specify one version of what browsers already do. Previously, it was left as part of DOM 0*, which was all the DOM stuff that browsers did before W3C specifications that was not made part of new specifications.
* DOM 0 is "…a mix (not formally specified) of HTML document functionalities offered by Netscape Navigator version 3.0 and Microsoft Internet Explorer version 3.0." W3C DOM Level 1 01-Oct-1998.
Related
I used to compile and insert JSX components via
<div key={ ID } dangerouslySetInnerHTML={ { __html: HTML } } />
which wrapped my HTML into a <div>:
<div>my html from the HTML object</div>
Now react > 16.2.0 has support for Fragments and I wonder if I can use that somehow to avoid wrapping my HTML in a <div> each time I get data from the back end.
Running
<Fragment key={ ID } dangerouslySetInnerHTML={ { __html: HTML } } />
will throw a warning
Warning: Invalid prop `dangerouslySetInnerHTML` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.
in React.Fragment
Is this supported yet at all? Is there another way to solve this?
Update
Created an issue in the react repo for it if you want to upvote it.
Short Answer
Not possible:
key is the only attribute that can be passed to Fragment. In the
future, we may add support for additional attributes, such as event
handlers.
https://reactjs.org/docs/fragments.html
You may want to chime in and suggest this as a future addition.
https://github.com/facebook/react/issues
In the Meantime
You may want to consider using an HTML parsing library like:
https://github.com/remarkablemark/html-react-parser
Check out this example to see how it will accomplish your goal:
http://remarkablemark.org/blog/2016/10/07/dangerously-set-innerhtml-alternative/
In Short
You'll be able to do this:
<>
{require('html-react-parser')(
'<em>foo</em>'
)}
</>
Update December 2020
This issue (also mentioned by OP) was closed on Oct 2, 2019. - However, stemming from the original issue, it seems a RawHTML component has entered the RFC process but has not reached production, and has no set timeline for when a working solution may be available.
That being said, I would now like to allude to a solution I currently use to get around this issue.
In my case, dangerouslySetInnerHTML was utilized to render plain HTML for a user to download; it was not ideal to have additional wrapper tags included in the output.
After reading around the web and StackOverflow, it seemed most solutions mentioned using an external library like html-react-parser.
For this use-case, html-react-parser would not suffice because it converts HTML strings to React element(s). Meaning, it would strip all HTML that wasn't standard JSX.
Solution:
The code below is the no library solution I opted to use:
//HTML that will be set using dangerouslySetInnerHTML
const html = `<div>This is a div</div>`
The wrapper div within the RawHtml component is purposely named "unwanteddiv".
//Component that will return our dangerouslySetInnerHTML
//Note that we are using "unwanteddiv" as a wrapper
const RawHtml = () => {
return (
<unwanteddiv key={[]}
dangerouslySetInnerHTML={{
__html: html,
}}
/>
);
};
For the purpose of this example, we will use renderToStaticMarkup.
const staticHtml = ReactDomServer.renderToStaticMarkup(
<RawHtml/>
);
The ParseStaticHtml function is where the magic happens, here you will see why we named the wrapper div "unwanteddiv".
//The ParseStaticHtml function will check the staticHtml
//If the staticHtml type is 'string'
//We will remove "<unwanteddiv/>" leaving us with only the desired output
const ParseStaticHtml = (html) => {
if (typeof html === 'string') {
return html.replace(/<unwanteddiv>/g, '').replace(/<\/unwanteddiv>/g, '');
} else {
return html;
}
};
Now, if we pass the staticHtml through the ParseStaticHtml function you will see the desired output without the additional wrapper div:
console.log(ParseStaticHtml(staticHtml));
Additionally, I have created a codesandbox example that shows this in action.
Notice, the console log will throw a warning: "The tag <unwanteddiv> is unrecognized in this browser..." - However, this is fine because we intentionally gave it a unique name so we can easily differentiate and target the wrapper with our replace method and essentially remove it before output.
Besides, receiving a mild scolding from a code linter is not as bad as adding more dependencies for something that should be more simply implemented.
i found a workaround
by using react's ref
import React, { FC, useEffect, useRef } from 'react'
interface RawHtmlProps {
html: string
}
const RawHtml: FC<RawHtmlProps> = ({ html }) => {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!ref.current) return
// make a js fragment element
const fragment = document.createDocumentFragment()
// move every child from our div to new fragment
while (ref.current.childNodes[0]) {
fragment.appendChild(ref.current.childNodes[0])
}
// and after all replace the div with fragment
ref.current.replaceWith(fragment)
}, [ref])
return <div ref={ref} dangerouslySetInnerHTML={{ __html: html }}></div>
}
export { RawHtml }
Here's a solution that works for <td> elements only:
type DangerousHtml = {__html:string}
function isHtml(x: any): x is DangerousHtml {
if(!x) return false;
if(typeof x !== 'object') return false;
const keys = Object.keys(x)
if(keys.length !== 1) return false;
return keys[0] === '__html'
}
const DangerousTD = forwardRef<HTMLTableCellElement,Override<React.ComponentPropsWithoutRef<'td'>,{children: ReactNode|DangerousHtml}>>(({children,...props}, ref) => {
if(isHtml(children)) {
return <td dangerouslySetInnerHTML={children} {...props} ref={ref}/>
}
return <td {...props} ref={ref}>{children}</td>
})
With a bit of work you can make this more generic, but that should give the general idea.
Usage:
<DangerousTD>{{__html: "<span>foo</span>"}}</DangerousTD>
My question is similar to that one:
Dijit Menu (bar) with link
I'm using Dijit Menu as in following listing:
<div data-dojo-type="dijit/Menu">
<div id="menuItem" data-dojo-type="dijit/MenuItem">
urlLink
</div>
</div>
But link is not working as it blocked by dojo.stopEvent in _onClick().
The question is:
How to remove dojo.stopEvent and make link inside <div id="menuItem" data-dojo-type="dijit/MenuItem"> work properly?
The issue:
I need to put inside <div id=menuItem"> some code, which has to receive onClick event.
P.S. Originally this is XPages code.
Well I fell in same problem, saw this post and the related other, but wasn't satisfied with the "onclick" solution :
it didn't work (for me) with keyboard navigation
it imposes to a add script element (onclick=...) in the declarative zone which is not what I expect for unobtrusive JavaScript
Finaly I digged further in dojo and decided to directly use the href attribute of first sub-node in the handler. My script section (derived from dijit menus tutorial) is then :
<script>
require([
"dojo/dom",
"dojo/parser",
"dojo/dom-attr",
"dojo/query",
"dijit/registry",
"dijit/WidgetSet", // for registry.byClass
"dijit/Menu",
"dijit/MenuItem",
"dijit/MenuBar",
"dijit/MenuBarItem",
"dijit/PopupMenuBarItem",
"dojo/domReady!"
], function(dom, parser, domattr, query, registry){
// a menu item selection handler
var onItemSelect = function(event){
dom.byId("lastSelected").innerHTML = this.get("label");
var achild = query("a", this.domNode)[0];
if (achild != null) {
var href = domattr.get(achild, "href");
if ((href != null) && (href != '') && (href != '#')) {
window.location.href = href;
}
}
};
parser.parse();
var setClickHandler = function(item){
item.on("click", onItemSelect);
};
registry.byClass("dijit.MenuItem").forEach(setClickHandler);
registry.byClass("dijit.MenuBarItem").forEach(setClickHandler);
});
</script>
That way I don't have to change anything in a menu of type
<ul><li>...</li></ul>
that works with JavaScript disabled, and links work fine with mouse and keyboard navigation when JavaScript is enabled. Simply don't forget the "class='claro'" in body element ....
What about this:
<div data-dojo-type="dijit/Menu">
<div id="menuItem" data-dojo-type="dijit/MenuItem"
onclick="window.location('http://url.com')">
urlLink
</div>
</div>
Working jsfiddle:
http://jsfiddle.net/KuyYX/
I have this fiddle, and can not make this work. I believe that the reason resides in that two li elements with a custom directive edit-in-place share scope.
The solution would be to say to the directive to create a copy of the scope that binds on the parent - can transclude help?
angular.module('bla', [])
.directive('editInPlace', ['$parse','$compile', function($parse, $compile) {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attribs) {
var inputStart = '<input style="border: 2 solid black" name="inPlaceInput" style="display:none" value="';
var inputEnd = '">';
scope.editModeAccessor = $parse(attribs.editInPlace);
scope.modelAccessor = $parse(attribs.ngBind);
scope.$watch(attribs.editInPlace, function(newValue, oldValue){
if (newValue){
console.debug("click");
console.debug("value: " + scope.modelAccessor(scope));
var inputHtml = inputStart + scope.modelAccessor(scope) + inputEnd;
element.after(inputHtml);
jQuery(element).hide();
scope.inputElement = jQuery("input[name=inPlaceInput]");
scope.inputElement.show();
scope.inputElement.focus();
scope.inputElement.bind("blur", function() {
blur();
});
} else {
blur();
}
});
function blur(){
console.debug("blur secondary");
if (scope.inputElement){
console.debug("blur secondary inputElement found");
var value = scope.inputElement.val();
console.debug("input value: "+ value);
scope.inputElement.remove();
jQuery(element).show();
scope.editModeAccessor.assign(scope, false);
scope.modelAccessor.assign(scope, value);
}
}
}
}
}]);
function ContactsCtrl($scope, $timeout){
$scope.contacts = [{number:'+25480989333', name:'sharon'},{number:'+42079872232', name:''}];
$scope.editMode = false;
var editedId;
$scope.edit = function(id){
$scope.editMode = true;
jQuery("#"+id).hide();
editedId = id;
//TODO show delete button
}
$scope.$watch('editMode', function(newValue, oldValue){
if (!newValue && editedId){
jQuery("#"+editedId).show();
}
});
}
<div ng-app="bla">
<div ng-controller="ContactsCtrl">
<h4>Contacts</h4>
<ul>
<li ng-repeat="contact in contacts">
<span edit-in-place="editMode" ng-bind="contact.number"></span>
<span edit-in-place="editMode" ng-bind="contact.name"></span>
<span id="{{$index}}" ng-click="edit($index)"><i class="icon-edit">CLICKtoEDIT</i></span>
</li>
</ul>
</div></div>
I think cloning the scope is not the best solution.
When creating a directive in angular, you should encapsulate all the functionality within the directive. You should also avoid mixing jQuery in when you don't have to. Most of the time (as in this case) you're just introducing unnecessary complexity. Lastly, classes are the best way of controlling display, rather than the style attribute on an element.
I took the liberty of rewriting your directive in a more "angular" way - with no jQuery. As you can see from the updated jsFiddle, it is simpler and cleaner. Also, it works!
This directive can be easily modified to add lots of additional awesome functionality.
app.directive( 'editInPlace', function() {
return {
restrict: 'E',
scope: { value: '=' },
template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement[0].focus();
};
// When we leave the input, we're done editing.
inputElement.prop( 'onblur', function() {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});
I have a Kendo UI datepicker with placeholder data. Here is the HTML:
<input type="text" class="datepicker"' placeholder="yyyy-mm-dd" />
Here is the JavaScript:
var start = $(".datepicker").kendoDatePicker({
format: "yyyy-MM-dd",
parseFormats: ["MM/dd/yyyy"],
change: startChange
}).data("kendoDatePicker");
The Kendo UI datepicker displays the placeholder data in the same style as user entered data. I would like to style the placeholder data differently. Specifically, I would like the text to be gray and italicized. When user enters data, the style changes to solid black (non-italicized). Any thoughts on how to do this?
Well, placeholder is an HTML5 attibute and isn't specic to Kendo controls. As I understand it Kendo doesn't offer any support for placeholder over what is supported by the browser, and remember that only some browsers support this attribute; IE does not.
Anyway, to style the placeholder you'll have to use vendor prefix CSS properties, see here.
I use this..it will work on your HTML and you can style it too :)
<script>
// This adds 'placeholder' to the items listed in the jQuery .support object.
jQuery(function() {
jQuery.support.placeholder = false;
test = document.createElement('input');
if('placeholder' in test) jQuery.support.placeholder = true;
});
// This adds placeholder support to browsers that wouldn't otherwise support it.
$(function() {
if(!$.support.placeholder) {
var active = document.activeElement;
$(':text').focus(function () {
if ($(this).attr('placeholder') != '' && $(this).val() == $(this).attr('placeholder')) {
$(this).val('').removeClass('hasPlaceholder');
}
}).blur(function () {
if ($(this).attr('placeholder') != '' && ($(this).val() == '' || $(this).val() == $(this).attr('placeholder'))) {
$(this).val($(this).attr('placeholder')).addClass('hasPlaceholder');
}
});
$(':text').blur();
$(active).focus();
$('form:eq(0)').submit(function () {
$(':text.hasPlaceholder').val('');
});
}
});
</script>
It's thick o'clock on Monday afternoon...
How do I add a pager to a projection list?
I have a list of 135 Recorded Species content items that comply with a Query. So how do I page them? :(
Checking Show Pager box in the Edit Projection page just adds:
<< Older Newer >>
links at the bottom of the page. The html rendered is, for example:
<ul class="group pager" shape-id="3">
<li class="page-next" shape-id="3">« Older
</li>
<li class="page-previous" shape-id="3">Newer »
</li>
</ul>
Ie:
<< Older increases the page number. Newer >> decreases the page number. This looks like a bug to me, as I would expect Previous and Next links, as well as page number links. Not older and newer...
Is there some module that needs disabling?
This is a feature: the default frontend pager is just like that (page 1 is always the newest page).
For a richer pager just take a look at the one in the TheAdmin theme (Views/Pager). This below is a stripped down version of it, displaying also the page numbers:
#{
Model.PreviousText = T("<");
Model.NextText = T(">");
var routeData = new RouteValueDictionary(ViewContext.RouteData.Values);
var queryString = ViewContext.HttpContext.Request.QueryString;
if (queryString != null)
{
foreach (string key in queryString.Keys)
{
if (key != null && !routeData.ContainsKey(key))
{
var value = queryString[key];
routeData[key] = queryString[key];
}
}
}
if (routeData.ContainsKey("id") && !HasText(routeData["id"]))
{
routeData.Remove("id");
}
Model.Metadata.Type = "Pager_Links";
IHtmlString pagerLinks = Display(Model);
Model.Classes.Add("selector");
var pageSizeTag = Tag(Model, "ul");
if (Model.RouteData != null)
{
foreach (var rd in Model.RouteData.Values)
{
routeData[rd.Key] = rd.Value;
}
}
}
#if (Model.TotalItemCount > 1)
{
<div class="pager-footer">
#pagerLinks
</div>
}