Ractivejs: how to collect values from dynamically added components? - ractivejs

Ref: this jsbin
My component has a select box:
var ListItem = Ractive.extend({
template:`<select value='{{color}}'>
<option disabled selected value=null>select color</option>
<option value='red'>red</option>
<option value='green'>green</option>
<option value='blue'>blue</option>
</select>`
})
And the main app can dynamically add components by clicking 'Add':
var ractive = new Ractive({
el:'#container',
template:`<p on-click='#this.add_comp()'>Add</p>
<p>{{colors}}</p>
{{#list}}
<listItem />
{{/list}}`,
data:{ list: []},
computed:{
colors(){
var items = this.findAllComponents('listItem')
cols=[]
for(var i=0; i<items.length; i++){
cols[i] = items[i].get('color')
}
return cols
}
},
components:{ listItem: ListItem },
add_comp(){ this.push('list',{})}
})
I need to collect the values of the components' select boxes into the parent as an array, e.g. ['red','green','blue']
But the array is not updating when components are added and colors selected.
Any insight you can offer would be much appreciated. Thanks in advance.

You should "encapsulate" your components by using their own data. Values from outside the component is passed as arguments to the component and as data is binded, you will always use your "parent" data, not the data inside the component.
So this is the component:
var ListItem = Ractive.extend({
data: { color: "" },
template:`<select value='{{color}}'>
<option value=null>select color</option>
<option value='red'>red</option>
<option value='green'>green</option>
<option value='blue'>blue</option>
</select>`
})
As you can see it has it's own data block...
Now we "inject" the data from our parent:
var ractive = new Ractive({
el:'#container',
template:`<p on-click='#this.add_comp()'>Add</p>
<p>{{colors}}</p>
{{#list}}
<listItem color="{{this.color}}" />
{{/list}}`,
data:{ list: []},
computed:{
colors(){
var items = this.get('list');
cols=[]
for(var i=0; i<items.length; i++){
cols[i] = items[i].color;
}
return cols
}
},
components:{ listItem: ListItem },
add_comp(){ this.push('list',{ color: "" })}
})
As you can see in "parent" component we pass a local value to the "child" component which is binded. Once we change color property of child component, list property in "parent" component will get updated.
Another important piece is that computed colors() property contains a depency to list property using this.get('list') inside.
I hope this answers your question.
Best regards,
Andy

AFAIK, Ractive dependencies are based on data keypaths, so that's my guess as to why calling this.findAllComponents('listItem') does not add a dependency for it. I tried adding a call to this.get('list'), but that makes the computed get called before the new component is added. I know you can defer an observer until after the template rendering happens, so you might need to use an oberserver instead of a computed.

Related

custom multi select control in alfresco activiti stencil

By following this link Alfresco custom control in stencil i have made custom multi select control with the same steps as mention in the post (Alfresco Activiti) , multi select works fine but problem i am facing rite now in visibility operation is not working of the control for example there is a text field and in its visibility section i am applying condition whenever value of multi select control value is middle and high hide this control as mentioned in the attached image. . code for multi select custom control is
<div ng-controller="multiselectController">
<select name="multiselect" multiple ng-model="field.value"
ng-options="option.code as option.name for option in field.options"
class="form-control ng-pristine ng-valid ng-scope ng-valid-required ng-touched"
>
<option value="">--Select State--</option>
</select>
</div>
angular controller code is
angular
.module('activitiApp')
.controller('multiselectController',
['$rootScope', '$scope', '$http',
function ($rootScope, $scope, $http) {
// that responds with JSON
$scope.field.options = [];
// in case of array values without rest services
if($scope.field.params.customProperties.ElxwfOptionsArrayMultiselect){
$scope.field.options = JSON.parse($scope.field.params.customProperties.ElxwfOptionsArrayMultiselect);
} else($scope.field.params.customProperties.ElxwfRestURLforMultiselect) {
$http.get($scope.field.params.customProperties.ElxwfRestURLforMultiselect).
success(function(data, status, headers, config) {
var tempResponseArray = data.RestResponse.result;
for (var i = 0; i < tempResponseArray.length; i++) {
var state = { name: tempResponseArray[i].name };
$scope.data.states.push(state);
}
}).
error(function(data, status, headers, config) {
alert('Error: '+ status);
tempResponseArray = [];
}
);
}
}]
);
help me in this regard.
Likely this is because your visibility code is not expecting an array.
You need to test for array contains rather than equal and not equal.

Updating model value onChange in Meteor + React places cursor at the end of the string

I am using Meteor with React. Consider this simple component below. There is a local mini-Mongo collection CommentsCollection. The component will insert a row in it when componentWillMount will be called. getMeteorData will return the first record in the collection and we'll be able to modify the title. Problem: if I place my cursor at the start of the title and start typing, after the first character update the cursor will jump to the end of the string and the rest of my typing will be placed there. How do I work around this?
CommentsCollection = new Meteor.Collection(null); // Local mini-mongo collection
EventTestComponent = React.createClass({
mixins : [ReactMeteorData],
componentWillMount(){
CommentsCollection.insert({title:"test title", message:"some test message"});
},
getMeteorData(){
return {
comment: CommentsCollection.findOne()
};
},
handleTitleChange(e){
CommentsCollection.update({_id: this.data.comment._id}, {$set:{title: e.target.value}});
},
render(){
if(this.data.comment) {
return (
<div>
<input type="text" value={this.data.comment.title} onChange={this.handleTitleChange}/>
</div>
);
}else{
return <div>Loading...</div>
}
}
});
I came up with this solution right after I posted the question:
<input type="text"
defaultValue={this.data.comment.title}
onKeyUp={this.handleTitleChange}/>
So: change value to defaultValue, and onChange to onKeyUp. Works like a charm!

Meteor data contexts - data not available inside template #each

I'm trying to compare two variables to see if they match.
This is to decide if I need a selected attr on an <option>.
The template looks like this:
<select>
<option disabled>Please choose...</option>
{{#each themes}}
<option {{selected}}>{{this.themeName}}</option>
{{/each}}
</select>
In the template helper I set a currentTheme var like so:
currentTheme: function() {
return this.theme;
}
The trouble is that this here is different to this within the #each loop above and placing {{currentTheme}} inside the #each renders nothing. Basically, I can't compare currentTheme with this.themeName to see if they are the same because one is always undefined :(
So... I'm wondering what I would have to do inside
selected: function() {
// ???
}
Many thanks!
As explained in this Discover Meteor blog post, since Meteor 0.8 you can pass the parent context as an argument to a template helper using the .. keyword.
<select>
<option disabled>Please choose...</option>
{{#each themes}}
<option {{selected ..}}>{{this.themeName}}</option>
{{/each}}
</select>
selected: function(parentContext) {
return this.themeName === parentContext.theme ? "selected" : '';
}
In this case, the currentTheme template helper would be unnecessary if you're using it just for this functionality.
You can use UI._parentData():
selected: function() {
// ???
var dataOutsideEach = UI._parentData(1);
if(currentTheme && this.theme) return "selected";
}
If that doesn't work, try removing the '1' and placing '0' or '2' instead (not quite sure about which). The integer represents the number of parent views to look for the data context through.

Knockout 2 way foreach binding DOM changes update Model

If I have a list on a page, and it is using knockout's foreach binding to display list items, then something else updates the DOM to add an extra list item. If there any way I can get knockout to detect that DOM change and update its model to add the new item to the observableArray?
Here is a fiddle which shows the problem...
http://jsfiddle.net/BGdWN/1/
function MyViewModel() {
this.items = ko.observableArray([
{ name: 'Alpha' }, { name: 'Beta' }, { name: 'Gamma' }, { name: 'Delta' }
]);
this.simpleShuffle = function() {
this.items.sort(function() {
return Math.random() - 0.5; // Random order
});
};
this.simpleAdd = function() {
$("#top").append("<li>New item</li>");
}
}
ko.applyBindings(new MyViewModel());
It has 2 lists bound to the same observableArray, click the addItem button and you can see that the DOM is updated to include the new list item in the top list, but I would like the second list to be updated too, all via the model.
It seems that knockout ignores DOM elements that it didnt render, you can see this by clicking the shuffle button, it leaves the new items there. I would have expected it to remove them and do a full re-render.
Please don't answer with "Just add the item to the observableArray"
Take a look at the first link and the second link Interface MutationEvent
See Fiddle
$('#top').bind('DOMNodeInserted DOMNodeRemoved', function () {
alert('Changed');
});
I hope it helps.

knockout bind text label to dropdown value selected option text

Is there a simple way to bind the textbox of a div to change based on the text value of the selected option in a dropdown on the same page?
<div data-bind="text: dropdownValue"></div>
<select>
<option value="1">Value1</option>
<option value="2">Value2</option>
</select>
Please note, I don't want to put the values into the select element using javascript. I'd like to bind to the value straight from the HTML. I can also include jQuery to make it work.
I was looking for similar functionality in something I was throwing together yesterday and couldn't find it, so I ended up just changing what I was storing in the value attributes. Sometimes that's the simplest solution.
Here's a quick and kind of ugly solution to the problem using jQuery:
HTML
<div data-bind="text: dropdownText"></div>
<select data-bind="value: dropdownValue" id="dropdown">
<option value="1">Value1</option>
<option value="2">Value2</option>
</select>
JS
function ViewModel() {
var self = this;
this.dropdownValue = ko.observable();
this.dropdownText = ko.computed(function() {
return $("#dropdown option[value='" + self.dropdownValue() + "']").text();
});
};
ko.applyBindings(new ViewModel());
Live example: http://jsfiddle.net/5PkBF/
If you were looking to do this in multiple places, it'd probably be best to write a custom binding, e.g.:
HTML
<div data-bind="text: dropdownValue"></div>
<select data-bind="selectedText: dropdownValue">
<option value="1">Value1</option>
<option value="2">Value2</option>
</select>
JS
ko.bindingHandlers.selectedText = {
init: function(element, valueAccessor) {
var value = valueAccessor();
value($("option:selected", element).text());
$(element).change(function() {
value($("option:selected", this).text());
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
$("option", element).filter(function(i, el) { return $(el).text() === value; }).prop("selected", "selected");
}
};
function ViewModel() {
this.dropdownValue = ko.observable();
};
ko.applyBindings(new ViewModel());
Live example: http://jsfiddle.net/5PkBF/1/
This is how I implemented a similar feature. I had an observable defined in my model called 'dropDownValue'. I also had an observable array called 'dropDownValues'. My HTML looked something like:
<span data-bind="text: dropDownValue"></span>
<select data-bind="options: dropDownValues, optionsValue: 'FieldText', optionsText: 'FieldText', value: dropDownValue"></select>
Note that I used the same field for optionValues and optionsText (not sure optionsText is really needed in this case). In my particular app 'dropDownValue' was pre-populated elsewhere, so when I opened a dialog box with the above select in it I wanted it to default to the previously populated value, and also bind it so that if the user changed it, I could reflect that change back in the database.

Resources