CANJS3 multiple instances of a component - javascript

With CanJS (3), I'm trying to insert twice the same component, but with a different behavior.
The 'parent' stache looks like :
<div class="container-fluid contents">
<...>
<my-component someParameters="value1"></my-component>
</...>
<...>
<my-component someParameters="value2"></my-component>
</...>
</div>
Thanks to to-child binding, the parameter is getting in the child viewModel, which is a simple DefineMap.
The child component is simple :
export default Component.extend({
tag: 'my-component',
viewModel: myviewmodel (a DefinedMap, with a can-connect attribute, and some others attributes),
view: view (some stache file),
events: {
'inserted': function(el) {
console.log('inserted my-component...');
},
}
});
So far so good, I achieve to have both on the same window, and while displaying the custom parameters, it shows the difference.
But then came troubles.
Both child component has a connection, (in the viewmodel), and I expect then to connect to the same IP, but to subscribe to a different channel.
It looks like CanJS doesn't instanciate two distincts viewmodel in facts, so I end up with the same instance of my can-connect object, which make the work impossible ...
If anyone has an idea of how to have two components on the same pagem but with two different scope, I'll be pleased to read it.
EDIT:
The real problem is the non-unicity of the "model" (ie the viewmodel.connect object)

The solution :
The can-connect object must be in a closure :
var behaviors = function() {return connect([
someBehavior,
anotherBehavior
],
{
url: {
getListData: "GET myURL"
}
})
};
And the DefineMap must be like :
var MyMap = DefineMap.extend({
connection: {value: behaviors},
parameters: ...
}
Some interesting reading :
value attribute doc

Related

Can't seem to get AngularJS one-way binding to work

I recently started using the (not so new) components for an old angular app. I'm trying to make some dumb components for trivial stuff like <button/>s and the like.
For some reason, I can't seem to get one way bindings to work!
The level binding in the difficultyButton component (in difficult-button.js) always returns undefined, but the onLevelChosen binding (again, in difficulty-button.js) seems to have the callback that the options component passed to it.
Do any of you see where I might've gone wrong?
Here is a jsbin link demonstrating this problem : http://jsbin.com/rixukuh/11/edit?html,js
Notice how the classes red, green and blue never get applied because they could never catch hold of the value of vm.level.
Also, the console always prints out LEVEL => undefined, irrespective of what button is clicked.
FULL CODE
Here's the full code, if more context is needed.
options.tpl.html
<div class="full-page-cover">
<div class="options-grid">
<!-- some random markup -->
<div class="buttons-grid">
<difficulty-button
level="easy"
on-level-chosen="vm.chooseDifficulty(level)" >
I'm just here for having fun!
</difficulty-button>
<!-- some more `difficulty-buttons` -->
</div>
</div>
</div>
options.js
import angular from 'angular';
import DifficultyButtonModule from './difficulty-button.js';
import template from './options.tpl.html';
class OptionsController {
constructor() { /* ... */ }
chooseDifficulty(level) { /* ... */ }
}
const OptionsModule = angular.module('options', [DifficultyButtonModule.name])
OptionsModule.component('options', {
template,
controller: OptionsController,
controllerAs: 'vm'
});
export default OptionsModule;
difficulty-button.tpl.html
<button
ng-class="[
'uk-button uk-button-large',
{
easy: 'uk-button-default',
medium: 'uk-button-primary',
hard: 'uk-button-danger'
} [ vm.level ]
]"
ng-click="vm.onLevelChosen({ level: vm.level })"
ng-transclude>
</button>
difficulty-button.js
import angular from 'angular';
import template from './difficulty-button.tpl.html';
const DifficultyButtonModule = angular.module('difficultyButton', []);
DifficultyButtonModule.component('difficultyButton', {
template,
bindings: {
level: '<',
onLevelChosen: '&'
},
controllerAs: 'vm',
transclude: true
});
export default DiffButtonModule;
when you do this
level="easy"
you're binding against easy property in your scope (like $scope.easy). Which of course does not exist. If you want to bind against the string value directly from your html, you need to use single quotes
level="'easy'"
and the same for the other levels. That will work fine.
If you still want to use bind against object, you will need to create them in them your scope. But since you need one way only, using string should work fine
Disclaimer: I haven't worked with components, so it might be that the explanation is incorrect. I have just worked with angularjs

Aurelia doesn't 'refresh' vm when navigating

Jolly good evening! In my Aurelia-App I'm using a viewModel to deal with various views via an navigationStrategy (reading out route-parameters and setting the view accordingly).
Navigation works baiscally well, there is one problem however:
When I keep navigating between routes that are based on the same viewModel, the viewModel doesn't 'refresh'. Only when navigating to a different route with a different viewModel first, and then back to the intended route, the contents are shown as expected.
It seems like the lifecycle-hooks of the component are not kicking in. Is there any way to trigger unbind() and detached() manually? Or is there a better way to do things generally?
Also the Route-Configuration seems a bit weird. When I'm taking away moduleId the app crashes, and when I'm taking away layoutViewModel the Data is not bound to the view. My Workaround for now is to assign an empty viewModel + an empty template. Am I using this wrong?
Big thanks!
configureRouter(config, Router) {
var getModelStrat = (instruction) => {
instruction.config.layoutView = "pages/templates/"+instruction.params.model+".html"
}
config.addAuthorizeStep(AuthorizeStep);
config.title = 'Aurelia';
config.map([
{
route: 'detail/:model/:id?',
name: 'detail',
moduleId: 'pages/empty',
layoutViewModel: 'pages/detail',
auth: true,
navigationStrategy: getModelStrat
},
{...}
]);
}
This is by design. Router will try to reuse existing view models.
If you need to override this per view model, then create determineActivationStrategy() method on it and return activationStrategy.replace:
import { activationStrategy } from 'aurelia-router';
export class SomeViewModel {
// ...
determineActivationStrategy() {
return activationStrategy.replace;
}
// ...
}
If you need to override this for each view model / route then take a look at Marton Sagi's answer for a similar question. Basically, all of your routes need to define activationStrategy: 'replace'.

vue JS not propagating changes from parent to component

I am pretty new to Vue Framework. I am trying to propagate the changes from parent to child whenever the attributes are added or removed or, at a later stage, updated outside the component. In the below snippet I am trying to write a component which shows a greeting message based on the name attribute of the node which is passed as property from the parent node.
Everything works fine as expected if the node contains the attribute "name" (in below snippet commented) when initialized. But if the name attribute is added a later stage of execution (here for demonstration purpose i have added a set timeout and applied). The component throws error and the changes are not reflected . I am not sure how I can propagate changes for dynamic attributes in the component which are generated based on other events outside the component.
Basically I wanted to update the component which displays different type of widgets based on server response in dynamic way based on the property passed to it .Whenever the property gets updated I would like the component update itself. Why the two way binding is not working properly in Vuejs?
Vue.component('greeting', {
template: '#treeContainer',
props: {'message':Object},
watch:{
'message': {
handler: function(val) {
console.log('###### changed');
},
deep: true
}
}
});
var data = {
note: 'My Tree',
// name:"Hello World",
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
function delayedUpdate() {
data.name='Changed World';
console.log(JSON.stringify(data));
}
var vm = new Vue({
el: '#app',
data:{
msg:data
},
method:{ }
});
setTimeout(function(){ delayedUpdate() ;}, 1000)
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
<greeting :message="msg"></greeting>
</div>
<script type="text/x-template" id="treeContainer">
<h1>{{message.name}}</h1>
</script>
Edit 1: #Craig's answer helps me to propagate changes based on the attribute name and by calling set on each of the attribute. But what if the data was complex and the greeting was based on many attributes of the node. Here in the example I have gone through a simple use case, but in real world the widget is based on many attributes dynamically sent from the server and each widget attributes differs based on the type of widget. like "Welcome, {{message.name}} . Temperature at {{ message.location }} is {{ message.temp}} . " and so on. Since the attributes of the node differs , is there any way we can update complete tree without traversing through the entire tree in our javascript code and call set on each attribute .Is there anything in VUE framework which can take care of this ?
Vue cannot detect property addition or deletion unless you use the set method (see: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats), so you need to do:
Vue.set(data, 'name', 'changed world')
Here's the JSFiddle: https://jsfiddle.net/f7ae2364/
EDIT
In your case, I think you are going to have to abandon watching the prop and instead go for an event bus if you want to avoid traversing your data. So, first you set up a global bus for your component to listen on:
var bus = new Vue({});
Then when you receive new data you $emit the event onto the bus with the updated data:
bus.$emit('data-updated', data);
And listen for that event inside your component (which can be placed inside the created hook), update the message and force vue to re-render the component (I'm using ES6 here):
created(){
bus.$on('data-updated', (message) => {
this.message = message;
this.$forceUpdate();
})
}
Here's the JSFiddle: https://jsfiddle.net/9trhcjp4/

Overriding a behavior in a directive : Best practices?

I have two directives, each displaying a list of documents, but in a slightly different way : One displays user-favorited documents, and the other one displays user-pinned documents. These two properties depend on two object members specified for each document, i.e. :
document = {
pinned: true,
favorite: false
};
Each directive displays a frame with its own title, according to the type of documents we want to display. For refactoring purposes, I use the same template for both, and specify varying template strings and objects in two controllers, each one dedicated to a directive. (i.e. the service member to call to get the documents we want is specified in a string, since the handling of these is exactly the same)
…Until I realized the two controllers were almost identical, the only thing that changed being… template strings.
So what I came up with is using the exact same controller and template (DocumentsPanel), but still with two directives, the only difference in them being link() :
function documentsPanelFavorites(templateService, resources) {
return {
restrict: 'A',
templateUrl: templateService.getUrl('Documents/Panel/documentsPanel.html'),
controller: 'DocumentsPanel',
scope: true,
link: link
};
function link(scope) {
//Used to show a favorite/pinned checkmark for each document entry
scope.documentOptions = {
showFavoriteCheckmark: true,
showPinnedCherkmark: false
};
scope.panelName = resources.text.MODULE_TITLE_FAVORITE_DOCUMENTS;
scope.className = 'favorites';
scope.noDocumentText = 'No favorite for this user';
// Used by the controller to know which method of the
// document dataService to call to get the listed documents
scope.documentList = 'favoriteDocuments'
// etc.
}
};
The documentsPanel.html template then uses these strings defined in the controller's scope via link().
Note: Another directive used to represent a document in a list is included in documentsPanel.html, that's why I set both showPinned and showFavorite options in each directive : It's the same directive that displays each document, and it is used elsewhere with all settings to true.
Would that be considered good practice? If not, what would be a better solution?
Thanks in advance!
I would expect documents="document | filter:{pinned:true}" and documents="document | filter:{favorite:true}"... Considering title, no document text, etc. I would first create config object and pass it to directive: config.title = '...', config.nodoctext = ... But if number of this strings params is too big, just create 2 templates.

How to handle nested views with Backbone.js?

In my app i have a few tagList, each one contains a few tags grouped by index_name.
I'm lost on how should i handle that with Backbone views.
I have a TagListView that extends Backbone.View, i guess i'll handle all events with this view.
My main question i : should i create a TagView with a render function that would be created & rendered for each tag in the TagListView render function ?
Here is what i do so far in my view : (is this ok for initialization ?!)
<ul id="strategy-tags">
<!-- initial data -->
<script type="text/javascript">
AppData.strategyTagList = $.parseJSON('<?php echo json_encode($strategyTagList); ?>');
strategyTagListView = new App.TagListView({el : "#strategy-tags", data: AppData.strategyTagList});
</script>
</ul>
Inside my core.js :
App.TagListView = Backbone.View.extend({
// dom event specific to this item.
events: {
},
initialize: function(options) {
this.el = options.el;
},
render: function() {
// let's construct the tag list from input data
_.each(options.data, function(index) {
// render index? <-- how ?
_.each(index, function(tag) {
// render tag? <-- how ?
console.log(tag);
});
});
return this;
}
});
Thanks a lot.
I would say yes, the benefit of the individual 'item' view being able to re-render individually is that if you make changes to the model behind such an item, only that item will need to be re-rendered by the browser. Which is best for performance.
It seems to be a question of granularity here, and one I've asked myself on occasion. My advice would be not to over do the views. It is akin to creating objects for everything in java - sometimes a simple string will suffice. If you find a case of increased granularity in the future you can always come back and change it.

Categories

Resources