I'm looking for a way update the absolute position values of skrollr dynamically. Relative positioning is out of the question as the body tag is the parent element and extends the entire length of the page. The object in question is of fixed position inside the body tag.
1) Is there a way to dynamically update the absolute values on an element without needing to re-instantiate the entire plugin? For instance changing "data-8000-start" to "data-9000-start". Using jQuery to update these seems a bit inadequate.
2) Using constants seemed to almost get me there, but as noted, requires that I re-instantiate the plugin. Is there a way to pass a constant like this into the refresh method instead?
Let me start by saying thanks to #Prinzhorn for this amazing tool. It really is a nice piece and has been a huge asset. I've built custom solutions for this kind of thing before, but this really adds an element of ease to it.
It doesn't appear there is a supported method for dynamically updating data attributes with Skrollr right now though. But as noted in a comment above there is a request for it. However I managed to hack something together that is fairly inefficient. I only performed this action on resize so it was used as minimally as possible.
I used JS to remove all attributes from the element. This seems terribly inneficeint, but any attempts I made to just have it remove the data attributes didn't work very well at all.
I added the necessary attributes back on that were removed. Then changed my dynamic values and applied new data attributes.
Finally, I made a refresh call to Skrollr and only applied it to the element that I had updated. This helped to ensure that only the necessary parts were being updated.
Just to update the accepted answer, right now you just have to change it dinamically and then use the refresh method:
var sk = skrollr.init();
sk.refresh();
Related
I am trying to support the same type of thing as React.Children
My code looks like
const elem = document.getElementById("profile")
const render = hyperHTML.bind(elem);
const name = elem.textContent
render`<b>Hi ${name}</b>`
So the API looks like
<div id="profile">alax</div> 🢂 <div id="profile"><b>Hi alax</b></div>
and I am using MutationObserver to rerender on content change
But if the content is changed. hyperHTML says its rending to the right element.. but the element keeps its innerHtml(No update)
I can see the <!--_hyper: -2001947635;--> is removed then the content is set but setting up the render & hyperHTML.bind again does nothing
Any thoughts would be great! Thx
Update
The fix to the above problem is to call hyperHTML.bind`` then your normal render using hyperHTML will work
Context -
I am using hyperHTML to create a custom element library(hyper-element)
My use case: I work in a mix-tech project (some people use jQuery)
Side note, on the why. I want to support something like partial templates
Example of a partial template:
<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
<div>{#name}</div>
</user-list>
Output:
<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
<div>ann</div>
<div>bob</div>
</user-list>
This is one use of setting custom content in an element you control
At the moment I have the setting of the content by 3-party working/re-rending
https://jsfiddle.net/k25e6ufv/16/
My problem is now: it is rending another custom element and getting the pass content to child element
It looks like hyperHTML is setting the child element's content in front to the element and creating the element without setting the content
Scroll down to bottom of source to see implementation!
https://jsfiddle.net/k25e6ufv/14/
Rending crazy-cats:
Html`
xxx: ${this.wrapedContent} zzzz
`
Current output:
wrapedContent: ppp time:11:35:48 ~ crazy-cats: **Party 11:35:48** xxx: zzzz
<crazy-cats>Party 11:37:21 xxx: <!--_hyper: -362006176;--> zzzz </crazy-cats>
Desired output:
wrapedContent: ppp time:11:35:48 ~ crazy-cats: xxx: **Party 11:35:48** zzzz
<crazy-cats> xxx: Party 11:37:21 zzzz </crazy-cats>
I will try to answer as best as I can, but I'll start saying that when asking for help, it'd be much easier/better to show the simplest use case you are trying to solve.
There is a lot of "surrounding" code in your fiddles so that I'll try to answer only to hyperHTML related bits.
hyper-element ?
I am not sure what's the goal of the library but hyperHTML exposes hyper.Component, and there's also an official HyperHTMLElement class to extend, which does most of the things you manually implement in your examples.
I'll keep answering your questions but please consider trying, at least, the official alternative and maybe push some change there if needed.
partial templates
hyperHTML pattern and strength is the Template Literal standard. Accordingly, to generate TL from the DOM would require either parsing of the content or code evaluation. Both solutions aren't the way to go.
Custom Elements require JavaScript to work, and without JS your partial template is useless and also potentially confusing for the user/consumer.
You don't want to define what to do with the data in the layout, you want to define a Custom Element behavior within the class that defines it.
That means: get rid of old-style in-DOM output, and simply use the Custom Element class to define its content. You maintain the related class only instead of maintaining a layout that has no knowledge about how the CE should represent that data.
TL;DR the following is a bad hyperHTML pattern:
<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]">
<div>{#name}</div>
</user-list>
all you want to do is to write this:
<user-list data="[{name:'ann',url:''},{name:'bob',url:''}]"></user-list>
but be careful, the data attribute in hyperHTML is special only if passed through the template literal. If you want to pass JSON to the component, call the attribute differently.
// hyperHTML data is special, no need to use JSON
render`<c-e data=${{as: 'it is'}}></c-e>`
Above snippet is different from having JSON as data attribute text so your example should use data-json name, and the class should remember to JSON.parse(this.dataset.json) in its constructor (or have an attribute observer that does that for you)
hyperHTML owns elements
When you write:
it looks like hyperHTML is setting the child element's content in front to the element and creating the element without setting the content
you are assuming you should care at all what hyperHTML does: you shouldn't.
The only thing you should understand is that hyperHTML owns the node it handles. If you trash those nodes via different libraries or manually, you are doing something wrong.
hyperHTML(document.body)`<p>hello ${'world'}</p>`;
// obtrusive libraries ... later on ...
document.body.textContent = 'bye bye';
// hyperHTML still owns the body content
hyperHTML(document.body)`<p>hello ${'world'}</p>`;
Above snippet is perfectly fine and totally wrong at the same time.
You don't update the body content manually, you don't interfere with its content via jQuery or other libraries, and you should never trash the content at all.
Once you chose hyperHTML to handle a bound context, that's it, you've made your choice.
This is true for pretty much every library on this world. If you use Angular to create something and you mess it all via jQuery, that breaks. If you write backbone templates and you mess later on with their content manually, that breaks.
If you bind an element to hyperHTML and you mess it up with other libraries, that breaks.
The only thing that won't break are wires, meaning the moment you create a wire, you can append it directly and that's actually a DOM node so it will be there, and it will be handled by hyperHTML.
Yet you should use hyperHTML to handle those changes, never jQuery or JS itself.
The output is right
When you say that the output should not contain the comment you are assuming you should care what output is produced via hyperHTML: you shouldn't!
hyperHTML uses comments as delimiters and these are absolutely fine for both performance, being unaffected by repaint and reflows, and for partial changes like the following one:
hyperHTML(document.body)`<p>${'a'} b ${'c'}</p>`
Both a and c will have a comment as anchor node to be able to update their content with anything later on.
hyperHTML(document.body)`<p>${[list, of, nodes]} b ${otherThing}</p>`
You change interpolations? All good, hyperHTML knows what to replace and where.
force-own the content
If you use a different template literal to re-populate a bound node you are trashing the cache and creating new content.
At that point you are better off with innerHTML because all the features of hyperHTML will be gone.
To start with, if your content can change so much, use an array.
hyper(document.body)`${['text']}`;
// you can clean up the text through empty array
hyper(document.body)`${[]}`;
// re-populate it with new content
hyper(document.body)`${['a', 'b', 'c']}`;
Above example is still better than changing template because all the optimizations for the content will be already there.
However, if you want to be sure the node the initial one created via hyperHTML, assuming no third parts script mutate/trash that node, you can use a wire.
const body = hyper()`<p>my ${'content'}</p>`;
document.body.textContent = '';
document.body.appendChild(body);
It's a bit extreme but at least faster.
As Summary
It looks like you are trying to sneak in hyperHTML into an application that trashes layout all the time through different third parts libraries.
Unless you create a closed Shadow DOM reference and you drop partial template through layout, you'll always have issues with libraries based on side effects with DOM content, libraries that mutates elements they don't own.
In hyperHTML the ownership concept is key, like in React you cannot change at runtime the defined JSX for the component, you should never try to change at runtime the defined template literal for hyperHTML.
Now, as much as I'd like to solve all your issues, I feel like it's right to ask you: are you sure hyperHTML is really the solution for your current app? It looks like surrounding side-effects caused by third parts libraries would constantly break your expectations if you don't use closed mode Shadow DOM and hyperHTML only to update your DOM.
I'm starting to develop a small JavaScript library and I want to make styling of HTML elements possible only through my API (because for some reason I need to have full control over styling).
So I want to make style property inaccessible (my API will access it through my style alias - not an ideal solution, but for other libraries like jQuery it will do the trick).
If I write this (inspired by this topic):
var box = document.getElementById('someElementId');
Object.defineProperty(box, 'style', {
get: function() {
throw 'you cant access style property';
}
});
box.style.color = 'red';
it works for box element only.
Is it possible to do this for all (existing and future) elements in Webkit, Firefox, and IE9+?
I've also tried this:
Object.defineProperty(HTMLElement, 'style', {...
but no luck.
Any help would be greatly appreciated!
Edit
As #Teemu suggested I can write HTMLElement.prototype instead of HTMLElement, and it works fine in FF and IE, but not in Chrome. And it looks like a Chrome bug. Sadly...
Edit2 - why do I need it
The main goal of a library I want to develop is to allow writing styles like:
element.setWidth('parent.width / 2 - 10');
In this case element's width should react on each changing of the parent's width.
But since onresize event is available only for window object (this article seems to be obsolete),
the only way I can "listen" modifying .style.width property is to perform my own API for styling.
And I want to restrict (or at least show warning) direct style modifying because it will break the elements' behavior.
From the comments you can tell that restricting access to the style property is probably not such a good route. But I understand from your question that your reason from trying to go this route is that the resize event only fires for the window object.
If you would be able to circumvent the whole restricting issue with a resize event on just any element, than may I suggest you look into Ben Alman's jQuery resize event plugin. I'm not sure whether you want to develop with jQuery, but even if you don't it may be worth it to read that plugin's code. Basically, what it does is make a hashtable of listeners mapped to the element(s) they are listening on and then in a polling loop (with setTimeout or setInterval) check the size of the elements in that map. If it has changed during the interval (250ms by default) it triggers the listeners itself. It works reasonably well. That specific plugin hooks into jQuery's event system, but you could just make your own addResizeEvent function or something to that effect.
Edit: Upon re-reading your question it dawns on me that it looks like you are trying to develop some mechanism to deal with the downsides of the CSS box model, e.g. that when you give an element 5px padding and a width of 50% it will end up being 10 pixels wider than half the parent container. Consider box-sizing: border-box if that is the case.
I've set up the CLeditor on a site I'm working on. Currently I'm setting it up so that as you type and edit within the editor, you can see a live preview of the results just above it, a lot like what you get when typing a StackOverflow question, though much more basic.
It works by simply copying the inner HTML of the iframe contents to another place on the page. However I've run into an annoying issue. When I use the alignment buttons (left, center, right), it adds the attribute align="right", for example, to the selected text. While it works in the editor, it does not work on the page itself, probably because that attribute is pretty much obsolete.
...
I actually figured out how to get around this issue while typing this question. Still, I'll post this question with my solution. Plus I have a relevant question to add to this.
Originally I tried applying the following CSS to the page:
div[align="right"] {
text-align:right!important;
}
This worked for initially loading the data onto the page, but while dynamically changing alignment in the editor, the live preview was not reflecting the changes. I thought at first that this was because the styles were applied at load time only.
Well, that was a brain fart because I know better than that. The real problem was that I was selecting a DIV element and the align attribute isn't necessarily applied to a DIV. Changing div[align="right"] to *[align="right"] works perfectly.
However, even though I found a workaround for this specific issue, I still can't figure out how the cleditor builds the HTML output for the iframe. Where does the align attribute come from in the code and how does it know to put it (and all of the other elements/attributes) into the HTML? If I had a way of manipulating this, I could simply tell it to use inline CSS for the alignment rather than the deprecated align HTML attribute. Please note that I do not wish to enable the cleditor's built-in "useCSS" feature.
Thanks for any information you can share, and please do not downvote this question just because I already solved the initial problem. I want this to be able to help others if they run into the same issue. (I'll also post my answer as an answer).
Applying the following CSS to the live-preview of the page works perfectly:
*[align="right"] {
text-align:right!important;
}
Don't forget to do the same for left and center as well.
All,
I'm working on an HTML page that includes a large data table.
As the user interacts with a variety of controls on that page, I'd like the data in the table to update, without reloading the page.
In other words, when the user changes the value of a control, that triggers a JavaScript function that performs a number of calculations, then "updates" the data in the table. In a typical scenario, anywhere from 50 to 500 cells would require updating.
My baseline approach:
assign each <td> cell a unique ID
in the JavaScript function, use document.getElementById() to get a reference to each cell that needs updating
use innerHTML to update those cells
This works fine, but it's probably very slow. E.g., does each call to innerHTML force the browser to re-render the entire table? In other words - does updating 500 cells trigger 500 're-renders'? Or, does the browser only re-render the table once the function is complete?
Long story short, what's the best way to do this?
My current approach?
recreate the entire table as a string in JavaScript, then use ONE call to innerHTML on the div that contains the table?
something else?
I recently watched Paul Irish's EXCELLENT (imo) presentation about optimizing JavaScript performance:
DOM, HTML5, & CSS3 Performance
In that, he describes making changes "off DOM"; unfortunately - that's mostly over my head, and his presentation doesn't include any actual code examples.
I'd prefer a straight JavaScript solution, but I'd be happy with a jQuery solution as well.
Many thanks in advance for any advice or insight.
I think using innerHTML is the way to go.
If each cell has it's own ID then the browser does not need to refresh the whole table to update a single cell.
I am using innerHTML in my current website that I am building and it works very quickly.
As long as you assign the function to the correct action that should work well.
If multiple cells will have the same value, then consider using classes,
in which case jQuery does make it easier to reference classes.
So basically, use your current approach.
Just re rendering each cell when needed is the quickest way (from my understanding and logic)
The approach you are taking is OK. However, it would be advisable to use jQuery or another abstraction library, which will take care of any quirks in the implementation of innerHtml in various browsers (some browsers are case sensitive etc.); this will make your life easier, and leave you to concentrate on developing your logic rather than working on browser idiosyncracies.
this question might be argumentative ... but I would suggest using something like jQuery jGrid plugin
it seems to me might be better that way
I've been using a rteEditor very sucefully until now.
The problem is in this line of code:
document.getElementById(rteName).contentWindow.document.execCommand('insertHTML', false, html);
I'm passing an ABSOLUTE path to the html var such as ("http://www.url.com/file.html").
But when it execute this insert command the output is ("../file.html");
Its possible to use a jQuery command instead?
Any Suggestions?
Have you tried using 'insertImage' instead of 'insertHTML'?
Edit:
'insertImage' just takes the url of the image and creates an img tag based on that.
You can get the image after inserting it with jQuery like this:
var img = $("img[src='imgUrl']");
with 'imgUrl' being the url of the image you add, and then add the needed attributes to that.
An example without using jQuery is here at line 123.
In my experience, working with native rich text editors (aka div's with contentEditable="true" or iframes with designMode set to on) is very difficult. The API is inconsistent across browsers and their behavior is often unexpected and buggy. Because of this I tend to use document.execCommand() as little as possible. Instead I tend to reply on direct DOM manipulation.
With that in mind, here's how I'd try to solve the problem you described:
Create the an in-memory image element and set the appropriate url.
Find the DOM node that contains user's cursor.
Insert the in-memory image element into the DOM node found in the previous step.
The code needed to implement the second step is somewhat tricky and varies hugely across browsers. I'll try to post a working example in the next day or two. I hope this helps in the mean time.
As far as I understand, and have experienced it myself, this is 1. inherent to the browser's HTML editing engine and 2. it happens only when the image that you are trying to insert, and the address you are running the HTML editor from are on the same domain.
As a solution, if your server/provider allows this, you could set up a second subdomain that points to www, for example
www2.example.com
and link to the image as
http://www2.example.com
this should have the result that the absolute link remains untouched.
upon saving the HTML, you just have to replace all occurrences of www2.example.com to www.example.com.
Another, maybe simpler, way would be to run the WYSIWYG editor on www2.example.com and inserting the proper absolute URLs.
I think because of security reasons, you can not specify complete url such as www.example.com.
I believe that you should be able to use jQuery.
You will probably want to use something along the lines of
$(rteName).find('body').html('<img src="http://www.example.com/" alt="...">
but probably with some changes to the selector(s).