Getting warning when using components recursively in VueJS - recursion

I'm making a recursive tree of components in VueJS. I have this kind of line in my item component:
<item v-for="i in items" v-model="i"></item>
It works fine, but Vue is giving me this warning:
You are binding v-model directly to a v-for iteration alias. This will
not be able to modify the v-for source array because writing to the
alias is like modifying a function local variable. Consider using an
array of objects and use v-model on an object property instead.
I'm confused about this warning, as my app seems to be working fine - I can add, move and delete nodes in my item tree just fine. What's up with this error message - should I worry?

You should worry, because you will not be able to use i bind to the model in other contexts, like watchers, or filters, etc.
This is not a good practice and the warning is valid.
Please consider creating an array in your data and add the your data to it as objects then do the v-for and binding. it is much safer.
I had a similar warning before and I could not deal with variables because they were considered local as the warning

The error is saying that changes to the param i passed as the v-model for your <item> component won't be reflected in the items property. This is because i is just an alias for the item and not the actual item.
If you don't need items to update when the model for each <item> component changes, then you don't need to worry. But you probably do want items to update since you're passing each element in items as a v-model.
In order to properly reference each item and avoid this warning, you can access each element in the array of items using the index. Like so:
<item v-for="(item, index) in items" v-model="items[index]"></item>

Related

Using collections of data in a store with React and Flux

I have a React + Flux application that uses multiple tab-pages of data. Each tab-page displays the same kind of data (e.g. an invoice), but from another object. Based on other posts I read on this subject, I decided to create a collection in the 'InvoiceStore' that contains an 'Invoice' object for every tab page that displays an invoice.
On every tab (in the details of the invoice), there are multiple widget-like components. These components are nested in multiple layers.
The challenge is that every component should know for which invoice object the data must be displayed. I know that I can arrange that by passing data (actual data or the 'key' of the item in the invoices collection) from the top component of an invoice (tab) to each of its descendants, but that would imply that the components in the middle would need to pass the received properties to its children. This seems like overkill to me.
Is it possible to have some kind of variable that has a scope of one component and its descendants? Or is there another sort of 'standardized' solution for this challenge?
Suggestions are really appreciated!
You can pass through props with JSX spread attributes: {...this.props}, which are based on the ES7 spread operator proposal.
This destructures all the props you received in the parent and passes them to the child. Occasionally, you don't want to pass all the props, and then you need to comb out (or add in) the props you want to pass explicitly. For that, it may be useful to use the new destructuring syntax available in ES6.
Example:
<MyChildComponent
foo={bar}
baz={qux}
{...this.props}
/>
There is also an undocumented, hacky technique using this.context, but that API is unstable and is likely to change. The spread operator is currently the technique recommended by the React team.
There is no "scope" concept in React, since React way prefers composition design pattern. The child component has to be as self-complete as possible, which should not know anything about the parent component except for the required props. Thus the object is required for each child component.
An alternative is to provide a public method getItem(itemId) in the InvoiceStore. But still one would have to pass the invoiceId by props.

Get data from the parent scope

Im trying to implement a single-page app with Ractive using components, and I need to have some page-wide options. I did like this:
...
data: {
options: {
someOption: true
},
...
Everything was fine while I used it like {{#if options.someOption}}, but then I faced a problem - rective.get('options.someOption') returns undefined (both with ractive.get('options')). Observing dont work as well. Is there any way to make my code understand me?
UPD. Accidentally solved problem with a portion of magic - get() starts working when I place {{options.someOption}} on template.
Ractive programmatic data access within an instance (includes components) can currently only "see" data that is:
Defined as data
Specified explicitly as a component parameter
Used as a reference in the template
For #1, you can include the options as default data and it will be available to all instances:
Ractive.default.data = {
options: {...}
}
Any new Ractive instance, including components, will have an options data property.
For #2, even if you have deeply nested components, you can have the parent of the component that needs the data include it as a parameter:
// Component somewhere in the "app" hierarchy.
// By referencing {{options}} in its template, it will find that data
// make it explicit on the widget component, which can then use it
// programmatically
<widget options='{{options}}'/>
For #3, you can include a "dummy" reference in the component template:
// by using it in the template, it is now available for programatic access
{{#with options}}{{/with}}
Of course then there's #4, enhancing Ractive to allow same lookup in code as template

Render a meteor reactive variable from a database field

If I have a field in my DB that contains a string like "Hello {{currentUser}}," is there a way to allow that value to retain reactivity when rendered into a template? I am also looking to see if I can somehow inject my own variable into the output by running it through a helper and handling string replacement.
Thoughts?
One solution I've come up with thus far:
The message stored in the db is something like: "Hello, [user], how are you?"
I then render the message from the DB as usual with {{#each}} and a predefined template.
When the message is actually rendered, I pass it through a helper. The helper replaces all []'s with <span class="$1"></span> so that I can target each item directly.
Once the message template's rendered() is called, I know that the message body contains the cleaned and prepped content (with the spans), so I use this.$('.user').each() and loop over each instance of the spans.
I've also created a special template in my page called 'placeholderUser' that only contains a call to {{user}}. I've added Template.placeholderUser.user = function(){} to the code to pull through a value and maintain reactivity.
Whew! Now that I have the structure set up, when looping through in the "each," I can call:
UI.insert(UI.render(Template.placeholderUser), el), which will render the template in the given span and maintain all reactivity.
It's super hacky, but it works. Any other, better, solutions out there?

Executing an item in the package as a Dreamweaver Template

Does anybody know if it is possible in a compound template to use a string item in the package and execute it as if were a dreamweaver template? And whether you apply the same method to other mediators (like razor)?
Thanks
Mark
I suspect this is not possible.
Package.EvaluateExpression may be useful, but as the name suggests it'll only work on expressions, not large snippets of code with embedded expressions (i.e. TEL)
Engine.GetMediator expects a Template and returns the appropriate Mediator for it. Your problem then is that the IMediator interface only defines the Transform method, which requires an Engine, a Template and a Package.
I can't think of any elegant ways around these. Maybe write your own Mediator, but that would still expect a Package, not a string, so you'd have to first store the string based Item from another TBB.
My advice: Sounds like you need to go back to the drawing board and find an alternative solution to your problem.
I'm afraid that won't be possible on just any item in the Package, since the Engine expects Templates to be based on Tridion items.
If your Template Item is based on a Tridion Item you can probably get pretty far by starting at the Engine.GetMediator method. If it isn't, you'll have to find some way to turn it into a valid Template object.
Template template = ...
IMediator mediator = engine.GetMediator(template);
mediator.Transform(engine, template, package);
When I have to create a Component object from a Tridion-based Item in the Package, I normally do something like this:
Component component = new Component(item.GetAsXmlDocument().DocumentElement,
engine.GetSession);
I haven't tried, but expect that you can do the same for a Template - given that you start with a valid Item from the Package representing a Template to begin with. You can probably clone the XML from an existing Item or find some other way to fake it.
If you get this to work, it will work across all registered template types. The Engine provides no special treatment for the types that come with Tridion.

Making sure my extended lists always show "current" data?

When you create a Data Extender for a CME list – for instance to add a column for the Schema as in this example – it all works fine and dandy whenever you do actions that force a List reload.
However, some actions don’t force a list reload (like editing a component in a folder, then saving & closing) and it looks like Anguilla is loading the data for the item that changed using a different mechanism that loads only the data for the item in question (which makes sense).
If I would want my extended list view to behave properly and also load my additional attributes whenever a given item changes (instead of only when the list view is reloaded) what else do I need to do?
I found how Anguilla takes care of this. When you implement a Data Extender, you are extending the information regarding the items displayed in the list, which basically means that you are extending the Data (Model) behind the item in question.
Each Item in Tridion has its own class in the Anguilla Framework, for example a Component has its own Tridion.ContentManager.Component javascript "class".
Having said this, and going back to the example that shows the schema name of the component, we are not actually extending the model, since that information is already available in the component. However, we need to overwrite the methods exposed on each used for displaying information in the lists the item is in, in this case a Component.
So, when we deal with a Data Extender, if we want a full implementation of this functionality, we not only need to define the data extender:
<ext:dataextender
name="IntelligentDataExtender"
type="Com.Tridion.PS.Extensions.IntelligentDataExtender,PS.GUI.Extensions">
<ext:description>Shows extra info</ext:description>
</ext:dataextender>
But also we need to define what's the column we are adding:
<ext:lists>
<ext:add>
<ext:extension name="IntelligentColumnExtender"
assignid="IntelligentDataColumnExtender">
<ext:listDefinition>
<ext:selectornamespaces/>
<ext:columns>
<column
xmlns="http://www.sdltridion.com/2009/GUI/extensions/List"
id="IntelligentData"
type="data"
title="Additional Info"
selector="#ExtendedInfo"
translate="String"/>
</ext:columns>
</ext:listDefinition>
<ext:apply>
<ext:view name="DashboardView" />
</ext:apply>
</ext:extension>
</ext:add>
</ext:lists>
Once we have this, the GUI will display the column we just added: "Additional Info"
Well, now we need to achieve the list refreshing when the item is edited/checked-out and in, etc...
For that, we need to extend the model and implement a few methods in the Object we are extending. In this example I am extending the Page object, so whenever a page is edited, the row in the list we want to update gets refreshed, together with the rest of the cells in the table.
To extend the model we need to define what types are we extending, in this example I am going to use the "Page" class as an example. First of all you need to define the model extension in the config file of your Editor:
<cfg:group name="Com.Tridion.PS.Extensions.UI.Model"
merger="Tridion.Web.UI.Core.Configuration.Resources.DomainModelProcessor"
merge="always">
<cfg:domainmodel name="Com.Tridion.PS.Extensions.UI.Model">
<cfg:fileset>
<cfg:file type="script">/Scripts/PSPage.js</cfg:file>
</cfg:fileset>
<cfg:services />
</cfg:domainmodel>
</cfg:group>
and
<ext:modelextensions>
<cfg:itemtypes>
<cfg:itemtype id="tcm:64" implementation="Com.Tridion.PS.Extensions.UI.PSPage" />
</cfg:itemtypes>
</ext:modelextensions>
As you can see I am extending the Page by using the "Com.Tridion.PS.Extensions.UI.PSPage" class that is defined in the Javascript file "/Scripts/PSPage.js".
The only method that handles the row refreshing is the following:
Com.Tridion.PS.Extensions.UI.PSPage.prototype.getListItemXmlAttributes
= function PSPage$getListItemXmlAttributes(customAttributes) {
var attribs = {};
var p = this.properties;
if (customAttributes) {
for (var attr in customAttributes) {
attribs[attr] = customAttributes[attr];
}
}
//This adds my custom column back when the item is updated
attribs["ExtendedInfo"] = p.extendedInfo;
return this.callBase(
"Tridion.ContentManager.Page",
"getListItemXmlAttributes",
[attribs])
};
As you can see I am implementing the "ExtendedInfo" attribute which is the one displayed in my additional column.
There's more than just adding a Data Extender when dealing with adding a column to our lists. I will write a post in my blog here to provide with a fully working example.
I hope it makes sense.
Well, Jaime correctly described how CME updates changed items in Lists. But I want to add some additional information on how List controls, domain model List and Items are interact with each other. This might help you building your own extension with similar functionality.
Most of the domain model List items inherit from Tridion.ContentManager.ListTcmItems class. On the moment when any List item, based on mentioned class, is loaded it will be registered in Lists Registry (and un-registered when the List is unloaded). This will allow Model to use registered Lists as source of static data for Items and to update changed Items data in these Lists.
Update Item static data
For example, we have loaded ListCategories and there is only one Category in the List:
var pub = $models.getItem("tcm:0-1-1");
var list = pub.getListCategories();
list.load();
// After list is loaded
list.getXml();
That getXml() returns an XML like (simplified):
<tcm:ListCategories>
<tcm:Item ID="tcm:1-4-512" Type="512" Title="Keys" />
</tcm:ListCategories>
After that, if you try to get some static data for Category "Keys" it will be already set:
var category = $models.getItem("tcm:1-4-512");
category.isLoaded(); // return false
category.isStaticLoaded(); // return false
category.getTitle(); // return undefined
category.getStaticTitle(); // return "Keys"!
That is possible because $models.getItem call will do two things: it will return an existing (or create a new) domain model object and call $models.updateItemData method with it. This method call will go through all registered Lists in the Lists Registry and for all Lists whose TimeStamp bigger than Item's Last Update TimeStamp will call list.updateItemData with the model object.
The updateItemData method will check if the passed Item is in the list and if it is, then the Item will be updated with the static data that is available from the List.
Updating data of changed Items in the List
When a domain model Item is modified (updated, removed, created new) one of these methods is called:
$models.itemUpdated
$models.itemRemoved
These methods will go through the Lists in Lists Registry and call list.itemUpdated (or list.itemRemoved). These methods will check is passed Item is contained in their List and if so they will update the List xml from the Item data.
For that purpose there is a getListItemXmlNode method in the Tridion.ContentManager.Item class. This method will build List xml node based on the array of attributes, provided by getListItemXmlAttributes method on the Item. That's what Jaime mentioned in his answer.
If the List xml was updated, one of these events will be fired on List object:
itemadd
itemupdate
itemremove
Listening to these events on a List object in your view will allow you to timely update your List Control.
So if you want this mechanism to work with your extension, stick to these rules:
If you are creating new domain model List object - it should inherit Tridion.ContentManager.ListTcmItems class or it should implement the getId(), itemUpdated(item), itemsUpdated(item), itemRemoved(item) and updateItemData(item) methods
If you want to see changes in List control - attach handlers to corresponding events on the domain model List object and update your List control
If you are creating new domain model Item - it should inherit Tridion.ContentManager.Item class and you should improve getListItemXmlAttributes method to return correct array of attributes for the List
The CME will indeed update the items in the list dynamically after the save occurs, without going to the server.
To do so, it calls a method named "getListItemXml" which returns the update XML element for the list. It will then update or add this element, which will update or add the item in the list view.
getListItemXml is a method of the different Model objects.
So how do you take advantage of this? I'm not sure.
Perhaps you could overwrite the method (or maybe getListItemXmlAttributes is best) with your own to add the additional data?
There is also an "itemupdate" event fired whenever an item is updated in the list.
You can hook into that by doing something like this:
var myEventHandler = function(event)
{
$log.message("Item updated. TridionEvent object passed: " + event);
}
var view = $display.getView();
var list = view.getListObject("uri-of-Folder");
list.addEventListener("itemupdate", myEventHandler);
I suppose you could use that to update the list entry for the item after the fact.
Be sure to call removeEventHandler at some point too.
None of this is optimal, obviously.
But I don't know of any extension point that would solve this particular problem.
I think I would (attempt to) implement this by monitoring the items in a folder periodically and updating that list after this polling mechanism had detected a change in that folder.
For example, I would write some javascript timeout or interval that runs in the background and checks the items in the current folder. If it detects a change, it triggers the update of the list.
Alternatively, you could also try to intercept the action that changed your list (e.g. the creation of a new item), maybe by means of an event system, and as such update your list. I don't think this is much different than the first approach, as I think it still implies some level of polling from the GUI side.

Resources