How to move components around - ractivejs

I'm using the 0.4.0 branch for the components as HTML files functionality. I'm trying to do the following: I have a component that controls the layout of a page. This component has some subcomponents as an array and displays them on different parts of the page based on some data in the subcomponent. Something akin to this (due to layout restrictions they have to be in different parts of the page):
<div id="section1">
<h1> Section 1 </h1>
{{# subcomponents}}
{{#isflagsection1(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
<div id="section2">
<h1> Section 2 </h1>
{{# subcomponents}}
{{#isflagsection2(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
<div id="section3">
<h1> Section 3 </h1>
{{# subcomponents}}
{{#isflagsection3(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
The flag is updated from controls within each component. this works great (the DOM is refreshed each time I modify the flag) except for one issue. Instead of performing a move, the subcomponent is recreated every time the flag changes, e.g. it's destroyed and created a new. This is unfortunate for my use case because of two reasons:
The subcomponent has a rather heavy creation cost (specially in mobile) since it performs some graphics work.
The subcomponent stores some private data (a
history of changes made to the model) that either a) gets lost when
it's moved along to another section or b) has to be stored in the
top component polluting it's data model.
So what I would like to know is, is there a way to "move" the component without deleting/recreating it?
Regards,
V. SeguĂ­

Yes - every Ractive instance has two methods that allow you to do this: ractive.detach() and ractive.insert(). Unfortunately the documentation is currently lacking, but here's how you use it:
// remove the instance from the DOM, and store a document
// fragment with the contents
docFrag = ractive.detach();
// insert the instance into the container element, immediately
// before nodeToInsertBefore (the second argument is optional -
// if absent or `null` it means 'insert at end of container'
ractive.insert( container, nodeToInsertBefore );
If you're removing the instance and immediately reinserting it, there's no need to detach it first - you can just do ractive.insert(). The arguments can be DOM nodes, but they can also be CSS selectors or the IDs of elements.
Here'a a JSFiddle demonstrating: http://jsfiddle.net/rich_harris/Uv8WJ/
You can also do this with inline components (i.e. <subcomponent/> as opposed to new Subcomponent(). In this JSFiddle, we're using ractive.findComponent('subcomponent') method to get a reference to the instance: http://jsfiddle.net/rich_harris/f28t5/.

Related

Using Data Layer to track event based rule

This is kind of confusing. suppose i have implemented a data layer with page details. The page has 2 articles, both gets redirected whenever clicked. Now suppose the tracking goes like this, Once the User clicks on the card, the name of card (h1) name will be reported in the tool. I have implemented this scenario without using data layer. (Looking for custom script in Adobe DTM data element?).
The code is mentioned below where i have defined the data layer. In DTM console I have created data elements, and mapped with the datalayer object. for Card name - DDO.pageData.cardname. Also created an event based rule and mapped with this data element. Problem here is, whenever i am clicking one article, it is taking h1 of both the article in a single evar. Ideally it should take the value of ONLY the article which is being clicked. Please suggest.
<article class="mu-item">
<a href="www.google.com" data-tags="test" target="_blank">
<div>
<h1>This is a test text for tracking</h1>
<p>This was the day when the South Stand at Old Trafford, the stadium where he played around half of his 758 matches for United, was officially renamed in his honour before his beloved Reds took on Everton in the Barclays Premier League.<br><br>
</p>
</div>
</a>
</article>
<article class="mu-item">
<a href="www.facebook.com" data-tags="{{displaytag_2}}" target="_blank">
<div>
<h1>This is new card</h1>
<p>This was the day when the South Stand at Old Trafford.<br><br>
</p>
</div>
</a>
</article>
<span>some text</span>
<script type="text/javascript">
DDO = {} // Data Layer Object Created
var pageObj = {};
var pageDOM = $('.mu-item');
pageObj.DestinationURL = $(pageDOM).find('a').attr('href');
pageObj.cardName = $(pageDOM).find('h1').text();
DDO.pageData = {
"pageName": document.title,
"DestinationURL":pageObj.DestinationURL,
"cardname":pageObj.cardName
}
</script>
<script type="text/javascript">_satellite.pageBottom();</script>
As others have mentioned, the only way to know which h1 value is relevant is to capture it during the click event.
As I mentioned in the comments of your other question
[..] DTM does not currently pass a reference to this to data elements created in the config area
There is no way around this. You must capture it during the click event as I showed you on your other question (custom script within a rule condition). As others have mentioned, making it pass through your data layer is technically an unnecessary step. There are pros and cons to actually doing it, but that's a different discussion.
Assuming you are determined to do it...
Go to Rules > Data Elements, and click Create New Data Element.
Name your data element according to whatever convention you use. For data layers, I personally like to name it the full data layer path, e.g. "DDO.pageData.cardName".
For Type, select "JS Object".
For Path, put the full path DDO.pageData.cardName
Click Save Data Element to save the data element.
Next, go to your Event Based Rule. It should still have your same Event Type (click), and Element Tag or Selector (article.mu-item).
In your Rule Conditions, select "Data > Custom" to add a custom js code box.
In here, you will still need to grab the information based on this. But instead of using _satellite.setVar() to create an on-the-fly data element, you will instead push the value to your data layer.
Example:
var articleTitle = $(this).find('h1').text()||'';
window.DDO
&&
window.DDO.pageData
&&
(window.DDO.pageData.cardName=articleTitle);
return true;
Note: I'm using jQuery syntax to get the h1 text. Based on your previous question you were having trouble with that at the time, so I showed you the vanilla js version. Not sure if you sorted that out or not, but you get the principle.
Now you can use your data layer referenced data element within the rest of your rule's fields %DDO.pageData.cardName%
Your problem is that you have multiple menu items and headlines. You would either have to create an array with all headlines (which would it make hard to get the correct entry for a click) or populate the datalayer only after a click on the element, so it always contains the data for the selected element.
However as I pointed out there is a good chance you do not need this. If you do an event based click rule with an css selector you have available a data element that DTM automatically creates - it's called %this% and contains the clicked DOM node. You should be able to get the text inside the headline with %this.textContent% (you have a nested paragraph in your headline, which I'm not sure is valid HTML, so you must allow event to bubble up in the event rule configuration).

How to force Meteor's Blaze to batch changes applied to #each block

I have a template which is mostly equivalent to:
<div class="line">
{{#each part in line}}
<span class="part">{part}</span>
{{/each}}
</div>
where the line: string[] is a partition of a single line into one or more parts. The problem is that when the content of line changes, the template tries to match old elements with new elements (which is fine) and applies changes one by one (which is not fine) to the DOM. In particular if the old value of line was ["Hello","world"] and the new one is ["Hello world"], then there is a short period of time when the user is presented new value of line[0] combined with old value of line[1], which is ["Hello world","world"]. Most of the time this gets unnoticed, but in case when the parts are long enough compared to the screen width, it might happen that <span class="part">Hello world</span><span class="part">world</span> does not fit into a single line, which in turn causes the whole further content to be moved one line lower, only to be later moved again one line higher when line[1] gets finally removed.
One solution which I currently use is to replace the whole #each loop with custom helper {{{ helpMeRenderThisLine line }}} which builds the HTML string manually, but obviously this violates separation of concerns and my code style.
I'm new to Meteor, but these are the directions I would like to investigate:
Is there a way to render a loop non-reactively? I've heard there was
something like #constant block, but it is no longer supported.
Is there a way to "batch" the whole updating process into a single DOM
reflow?
Is there a way to use the momentum package to remove the
flicker?
Is there a way to use a smaller Blaze template to render the
single line in non-reactive fashion?
Is there a way to render a loop non-reactively? I've heard there was something like #constant block, but it is no longer supported.
It is pretty straightforward to make a part of the the DOM non reactive: you just need to use a non reactive helper. E.g. in you template rendered or created function, you can attach your string array to your template instance (not a cursor using this.myStrings = Collection.find() but an array using this.myStrings = Collection.find().fetch(), this being your template instance).
You then return it in your helper using return Template.instance().myStrings
Is there a way to "batch" the whole updating process into a single DOM reflow?
Actually, it depends on how you refresh the original data. The above solution should do that. Keep in mind that you can nest templates to control the granularity of the DOM refreshes. Also make sure you control your subscriptions in case they are overlapping with the same published content. You can wait for every one to be ready using Template.subscribtionReady() (template level) or subscribtion.ready() (sub level)
Is there a way to use the momentum package to remove the flicker?
The flicker should go away if you refresh properly the data.
Is there a way to use a smaller Blaze template to render the single line in non-reactive fashion?
Not sure about what you ask here but a good practice would be to try to make component level templates, it might help in fine tuning the DOM rendering.
<div class="line">
{{#each part in line}}
{{> Part}}
{{/each}}
</div>

Collections in Create.js

According to Create.js' integration guide, it should be possible to create editable collections of blocks.
Relationships between entities allow you to communicate structured
content to Create.js, which will turn them into Backbone collections.
For example, to annotate a list of blog posts:
<div about="http://example.net/blog/" rel="dcTerms:hasPart">
<div about="http://example.net/my-post">...</div>
<div about="http://example.net/second-post">...</div>
</div>
This dcTerms:hasPart doesn't seem to be present explicitly in Create.js; it's probably from a vocabulary. How can one show to Create.js that this content is a collection and make it show the "Add" button? In my case, I simply have sections which contain <h2>, <h3> and <article>s.
EDIT: Using rel="hasPart" in my <section>, the button appears. Problem is Create always uses the first element as a template. In this case, it uses my <h2> as template, which is not what I intended. So my question now would be how to trigger the "add" event in my section?
Hackish solution:
Using javascript, I created a new section or article in the DOM, then restarted Create calling $('body').midgardCreate({...}) again. Create will now recognize the new block. When I edit some fields in the block, the "Save" button is enabled and I can submit the new block.

How to update partials

Hi there RactiveJS users, I'm new here and I'm very excited using RactiveJS but, I came up to wonder if I can update the template / data inside a Ractive instance .
I have the following modal window:
<div id="modal" intro-outro="fade:100">
<div id="content">{{>content}}</div>
</div>
And >content is a ranking template. Imaging that clicking on a player will need to overwrite the ranking with the playerProfile template and it's requested data. Is there another way this could be done aside teardown and then reinitialise the modal window with the playerProfile template ?
UPDATE
I forgot to mention that the window will wrap around 20 templates at the moment (which will grow in the future as the game gets more complex). The window I am talking about is the main modal window that shows every template that user triggers, like rankings, mails, reports, tribes, map etc.
Regards.
WORKAROUND
Marty's answer is correct but I'd rather not complicate myself and just create new instances of Ractive inside the #content div than create different instances of the whole modal window which will be shown on init and hidden on teardown.
If you just have two states (as opposed to open-ended dynamism), you can block them conditionally:
<div id="modal" intro-outro="fade:100">
<div id="content">
{{^selected}}{{>partial1}}{{/}}
{{#selected}}{{>partial2}}{{/}}
</div>
</div>
You can augment the data model after initial render. Supposed you had a player click handler:
ractive.on('playerSelected', function(e){
//however your data works...
$.get('/player?id=' + e.context.id, function(data){
ractive.set(e.keypath + '.player', data.player)
ractive.set(e.keypath + '.selected', true)
})
})
If you need a more dynamic approach, you can use a component to set the partial dynamically. Here's an example (http://jsfiddle.net/9Vja5/2/) of how you can do it in 4.0 (4.1 will offer a few improvements in this area). Use a component in the main template:
<div>
{{#reset}}
<dynamicPartial partial='{{partial}}'/>
{{/}}
</div>
Then set the template to the supplied partial in the component beforeInit:
Ractive.components.dynamicPartial = Ractive.extend({
beforeInit: function(o){
o.template = '{{>' + o.data.partial + '}}'
}
})
The partials need to either be defined globally, or on the dynamic component.
I'm using a partial because that's what you had in your question. You could set the template directly if you had a list of which template to assign. Or you could also set the template to a component via '<' + component + '/>'.
The reset is a bit of a hack to force Ractive to re-render the component:
r.observe('partial', function(){
r.set('reset', false)
r.set('reset', true)
})
Otherwise it would just change the data in same component instance, which wouldn't get it to recreate and assign a new template.

JQuery load() function disables links and :hover effects?

I'm trying to load content from a different file into a div element within the current file using the jQuery load() function. Nothing fancy, just loading it and that's it. However the links that are contained in the loaded file become "disabled", you cannot click them, and pseudo-classes like :hover seem to be left out as well. Is there a solution to this?
$(document).ready(function() {
$("div.content").load("content.html");
});
let's say content.html contains just this line:
xxx
When it is loaded into the <div class="content"> the link is not clickable. It is colored according to the css, however the :hover effect doesn't work, and it behaves like normal text - not a link. This is a problem because the content I'm trying to load has a couple of links, and none of them work after being load()'ed.
I believe your issue is:
You use $('div.content').load('content.html') to send a request for content to (later) be inserted into the DOM.
You then run some code to specify handlers for nodes using $(document).click, $(document).bind etc - but this code runs before the new nodes have been added to the DOM.
New nodes are then added when the .load call completes.
The behaviour that you defined on all the origional nodes isn't being followed on the new nodes.
If that is the issue your're describing - then you need to add all the same bindings to the new nodes once they're created.
i.e. you need to provide a callback to add the bindings to the new elements:
function on_data_loaded() {
$('div.content ...').hover(.....);
// etc.
}
$('div.content').load('content.html', null, onloaded);
(note that's not a particularly clean way of doing it, but it should explain what needs to be done).

Resources