Dojo update only part of template - dojo

I have created a widget based template like,
<div class="content">
<div>
<!-- rest of content-->
</div>
<div class="col-md-6">
<div class="panel" data-dojo-attach-point="sysinfo">
<ul class="col-md-12 stats">
<li class="stat col-md-3 col-sm-3 col-xs-6">Host:</br> <span><b class="value">{{hname}}</b></span>
</li>
<li class="stat col-md-3 col-sm-3 col-xs-6"># CPU:</br> <span><b class="value">{{cpu}}</b></span>
</li>
</ul>
</div>
</div>
</div>
How do I update only content of sysinfo ?
Till now I was doing,
var widget = this;
widget.template = new dtl.Template(widget.templateString);
var template = widget.template;
template.update(node, stats); // but it update complete content as node == content. I just want to refresh sysinfo.
I also tried,
template.update(this.sysinfo, stats); // but it throws exceptions
Any ideas?

As far as I can see is that when you're using dojox/dtl/_Templated as suggested in the documentation, there is no update() function available.
If you really wish for certain things, you will have to manually define a template and render that one (and replace the attach point), for example:
var subtemplate = "<ul data-dojo-attach-point='sysinfo'>{% for item in list %}<li>{{item}}</li>{% endfor %}</ul>";
var template = "<div><h1>{{title}}</h1>" + subtemplate + "</div>";
var CustomList = declare("custom/List", [ _WidgetBase, _Templated, _DomTemplated ], {
templateString: template,
subTemplate: new dtl.Template(subtemplate),
title: "Fruits",
list: [ 'Apple', 'Banana', 'Lemon' ],
_setListAttr: function(list) {
this.list = list;
this.sysinfo = domConstruct.place(this.subTemplate.render(new dtl.Context(this)), this.sysinfo, "replace");
},
_getListAttr: function(list) {
return this.list;
}
});
Normally, if you would update the template when the list is set, you could use this.render() inside the _setListAttr() function to update the entire template.
However, as you can see in the _setListAttr() function I'm replacing an attach point by a newly rendered Django template.
This results in only that part of the template being updated, in stead of the entire template. So {{title}} would remain the original value, even when changed.
A full example can be found on JSFiddle: http://jsfiddle.net/pb3k3/

Related

Dynamic computed property in html

I'm using php for creating set of computed properties in my app based on ID property of each object in my main array of data stored in property deals. So i have now computed properties like list_10_deals_cnt, list_20_deals_cnt, list_30_deals_cnt etc. Now, how can I create these dynamic created properties in span with class dials__cnt while looping my array of data? {{'list_'+el.id+'_deals_cnt'}} is not working as i wish, its display just a string like list_10_deals_cnt instead to display a computed value.
P.S. sorry about my english.
<div class="dials" id="app">
<div class="dials__column" v-for="(el, index) in deals">
<div class="dials__header">
<div>{{el.title}}</div>
<div>сделок: <span class="dials__cnt">{{`'list_'+el.id+'_deals_cnt'`}}</span>, <span></span> руб</div>
</div>
<draggable drag-class="draggable-ghost__class" class="dials__block" :list="el.items" group="deal" #change="log(el.id, $event)">
<div
class="dials__card"
v-for="(el, index) in el.items"
:key="el.title"
>
<div class="card__header">
<div class="card__title">{{ el.customer_name }}, {{ el.customer_company_name }}</div>
<div class="card__date">{{ el.created_at }}</div>
</div>
<div class="card__body">
<a :href="'/deal/detail/'+el.id" class="card__link">{{ el.title }}</a>
</div>
<div class="card__footer">
<span class="card__price">{{ el.price }} руб </span>
<span v-for="(tag, index) in el.tags" class="card__tag">{{ tag }}</span>
</div>
</div>
</draggable>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
deals: <?php echo json_encode($deals, JSON_UNESCAPED_UNICODE); ?>
},
computed: {
<?php
foreach ($deals as $k=>$v) {
echo 'list_'.$v->id.'_deals_cnt: function() {return this.deals['.$k.'].items.length},';
}
?>
},
methods: {
log: function(id, evt) {
if (evt.hasOwnProperty('added')) {
let dealID = evt.added.element.id;
console.log('сделка ID '+dealID+' перемещена в статус '+id+'. Отправляем аякс запрос на обновление статуса в бд');
// ajax for update
}
}
}
});
</script>
Hi
Problem 1
you will not be able to get the value of computed property by using this
{{`'list_'+el.id+'_deals_cnt'`}}
for the same reason as console.log('vari' + 'able') doesn't print out value of variable to the console.
( Vue evaluates whatever is in between {{ }} as an expression ).
Solution
I suppose, you can either use the deals property directly in html as shown below without using a computed property
<div class="dials__column" v-for="(el, index) in deals">
<div class="dials__header">
<div>{{el.title}}</div>
<div>сделок: <span class="dials__cnt">{{ el.items.length }}</span>, <span></span> руб</div>
</div>
----------- rest of the code
or you can create a computed property based on deals data property and use that to loop in html using v-for.
Problem 2
I don't think below code is valid php string. although it becomes irrelevant if you use first solution above.
<?php
foreach ($deals as $k=>$v) {
echo 'list_'.$v->id.'_deals_cnt: function() {return this.deals['.$k.'].items.length},';
}
?>
the ' inside ['.$k.'] should be escaped.
Solution
echo 'list_'.$v->id.'_deals_cnt: function() {return this.deals[\'.$k.\'].items.length},';

VUEJS V-FOR acting weird

I need a simple v-for to render object properties in a list.
<div
v-if="profileData && profileData.length > 0"
>
<ul
v-for="value in profileData"
:key="value.id"
>
<li>{{value.id}}</li>
</ul>
</div>
In the script:
profileData: {},
created() {
const userId = this.$route.params.userId
const currentUser = this.$store.getters.currentUser
const profileData = this.$store.getters.profileData
console.log('profileData in seeProfile: ', profileData) }
(profileData comes from an api response)
I did exactly the same in two other pages (just rendering different objects) and it worked.
With this code, in the console I get value is undefined.
If I remove :key="value.id" (it becomes red in the editor but it still works), and instead of the list items I type only {{ value }}}, then the object properties get rendered ( but in the ugly format of a js object). How can it be? What am I doing wrong?
Thank you
Your v-if will never show even if profileData has data, because you can't directly check for the length of an Object in javascript.
A few things:
You can't check for the length of an Object, it will return undefined. If you must use an object, then you'd have to check for Object.keys(obj).length.
let obj = {
first: {
name: "first",
meta: "data"
},
second: {
name: "second",
meta: "data"
}
};
console.log("Object.length is: ", obj.length);
console.log("Object.keys().length is: ", Object.keys(obj).length);
You're being redundant, you don't need to check for profileData and its length (and you don't need to > 0), you could simply check for v-if="Object.keys(profileData).length". If the Object has zero entries, then it won't show because if(0) is false.
I'd strongly recommend to work with arrays to iterate with v-for. I'd use computed properties and return an array, and iterate through that. Object reactivity works non-intuitively in JS, so you'll be scratching your head later when you try to find out why stuff isn't updating on your view:
computed: {
listData() {
let list = Object.values(this.profileData);
return list;
}
}
In view:
<div v-if="listData.length"/>
Also, don't use the array's entry index as your :key, because if you have another array with v-for, you'll have duplicated keys in your model. I'd use something like v-for="(item, key) in list" :key="'list-item-' + key"
Put the v-for on the li, not the ul.
<ul>
<li v-for="value in profileData"
:key="value.id">{{value.id}}</li>
</ul>
Also , if your your profileData is an object and not an array, you need to decide if you want to loop through the keys or values.
<ul>
<li v-for="value in Object.values(profileData)"
:key="value.id">{{value.id}}</li>
</ul>
<ul>
<li v-for="value in Object.keys(profileData)"
:key="value.id">{{value.id}}</li>
</ul>
Or use Vue's default behavior.
<ul>
<li v-for="(value,key) in profileData"
:key="value.id">{{value.id}}</li>
</ul>
The api has been changed, so the working code is slightly different from the original one.
Here's the template:
<div
v-if="listData.length"
>
<ul>
<li>Name: <b>{{ profileData.user.first_name }}</b></li>
<li>Surname: <b>{{ profileData.user.last_name }}</b></li>
<li>Username: <b>{{ profileData.user.username }}</b></li>
<li>Car: <b>{{ profileData.if_cars_owned }}</b></li>
<li v-if="profileData.if_cars_owned === true">
Car model: {{ profileData.user_car_type }}
</li>
<li v-if="profileData.if_cars_owned === true">
Car power: {{ profileData.user_car_powered_by }}
</li>
<li>Motorcycle: <b>{{ profileData.if_motorcycle_owned }}</b></li>
<li v-if="profileData.if_motorcycle_owned === true">
Motorcycle model: {{ profileData.user_motorcycle_characteristic }}
</li>
</ul>
</div>
Script:
created(){
const profileData = this.$store.getters.profileData
this.profileData = profileData
console.log('profile data in profilo: ', profileData)
},
I've also updated
<div
v-if="listData.length"
>
and in the script
computed: {
...,
listData() {
let list = Object.values(this.profileData);
return list;
}
},
following the advice of #Adrián S. Basave.
Thanks to anyone who tried to help.
x

How to get data from JSON? My v-for loop is not working

I'm trying to get data from a external json but the v-for loops does not work. Previously, with the same code I was able to show the data I don't know whats happens now.
This is the code i'm using:
`
<div class="row">
<div class="col-sm-4">
<h1>Llista Valors</h1>
<div class="card" style="margin-bottom: 2%" v-for="dada in list">
<div class="card-header">
Hora: {{dada.lists.hora}}
Preu: {{dada.lists.preu}}
</div>
</div>
</div>
<div class="col-sm-8">
<h1>JSON</h1>
<pre>
{{ $data }}
</pre>
</div>
</div>
</div>`
`
var url = "http://172.17.100.3/api/tarifa2.php";
new Vue({
el: '#main',
created: function () {
this.getApi();
},
data: {
lists: []
},
methods: {
getApi: function () {
this.$http.get(url).then(function (response) {
this.lists = response.body.valors;
});
}
}
});
</script>`
This is what i get:
Your data should be called list:[], not lists:[] to match your template. Or rename your template to in lists.
For each dada in lists, there is no dada.lists.hora or dada.lists.preu. There is only dada.preu or dada.hora.
Each dada in lists is like saying lists[0] (or whatever index).
So try changing the template to just {{data.hora}} etc as needed (after doing the list/lists adjustment per above).

Empty TabContainer in Dojo Dialog

The problem is that the TabConainer inside the Dialog is empty after opening although selected="true" is given (see the screenshot below). The content is called with dojo/html html.set(node, contentHTML, {parseContent: true});
When changing the tab by clicking on another one the content appears and the class "dijitVisible" is set for this div as it should be from the beginning. The attribute nested="true" is necessary since otherwise three select bars are shown over the tabContainer.
What can I do so that the content appears from the start on?
<div data-dojo-type="dijit/Dialog" id="formDialog" data-dojo-id="formDialog" title="Edit member data">
<div id="formContent" class="dijitDialogPaneContentArea" data-dojo-attach-point="formContent">
</div>
</div>
Update:
Here is the whole javascript for getting the content
getForm = function(formID, urlAction){
var contentHTML;
var xhrArgs = {
url: urlAction,
handleAs: "text",
load: function(data){
contentHTML = data;
},
error: function(error){
contentHTML = text_error_unexpected + ": " + error;
},
handle: function(error, ioargs){
var node = dom.byId(formID);
html.set(node, contentHTML, {parseContent: true});
}
}
var deferred = dojo.xhrGet(xhrArgs);
};
Update 2:
This is the content that gets called and inserted in the above div "formContent" (I thought I make the description as simple as possible and lost some details on the way)
<div id="form" data-dojo-type="dijit/form/Form" data-dojo-attach-point="form" encType="multipart/form-data" action="#">
<div style="width: 450px; height: 370px;">
<div data-dojo-type="dijit/layout/TabContainer" nested="true">
<div data-dojo-type="dijit/layout/ContentPane" title="Personal data" selected="true">
Content 1
</div>
<div data-dojo-type="dijit/layout/ContentPane" title="Detailed data">
Content 2
</div>
<div data-dojo-type="dijit/layout/ContentPane" title="Contact data">
Content 3
</div>
</div>
</div>
</div>
Have you tried calling either dialog.resize() or tabcontainer.layout() after adding it to the dialog?
I am not sure as to how the code below will place contents inside the first ContentPane (title="Personal data"). I am assuming that the parameter formID = "form"
html.set(node, contentHTML, {parseContent: true});
I can suggest an alterantive.
Use an id with the content pane as shown below.
<div id="content1" data-dojo-type="dijit/layout/ContentPane" title="Personal data" selected="true">
Content 1
</div>
Then use dijit/registry to get the contentpane widget in the handle function call as shown below.
handle: function(error, ioargs){
var content= registry.bId(formId); // over here formId = "content1"
content.set("content","<p>This is content for <b>Personal Data</b></p>");
//content.set("content", contentHTML);
}
EDIT 1
This is may be one possible solution.
#Richard had suggested dialog.resize(), which I did try to put it after the html.set code but it would not work.
What I have noticed is that the html.set takes some time to execute and the dialog.resize() does not work because it is
called before the completion of the html.set call.
html.set also complicates the issue as it does not provide any handle (promise object) to let us know when it has finished execution.
so the below solution uses a setTimeout call to delay the execution of the dialog.resize(). Hence would advice to put the value of delay time depending upon some actual UI testing.
Modified code.
handle: function(error, ioargs){
var node = dom.byId(formID);
html.set(node, contentHTML, {parseContent: true});
var dialog = registry.bId("formDialog");
setTimeout( function(){
dialog.show();
dialog.resize();
},2000) // time delay of 2 seconds
}

Knockout observable array

In my ViewModel for my MVC4 application I have some code to get names from an ajax call and populate a simple control within my page, which is using Bootstrap 3. As you can see below I have a hard-coded array which works perfectly. With the ajax call, I see the data in the UI but it does not update my control and I have NO idea why. I have verified the data exists and I have also tried setting self.Names = ko.observableArray within the ajax call. Is there a simple reason why? As I said I see the data within my form in both scenarios but I am not seeing the update I expect.
$(document).ready(function () {
function ViewModel() {
//Make the self as 'this' reference
var self = this;
//Declare observable which will be bind with UI
self.Name = ko.observable("");
var Names = {
Name: self.Name
};
self.Name = ko.observable();
//self.Names = ko.observableArray([{ Name: "Brian" }, { Name: "Jesse" }, { Name: "James" }]);
self.Names = ko.observableArray(); // Contains the list of Names
// Initialize the view-model
$.ajax({
url: '#Url.Action("GetNames", "Home")',
cache: false,
type: 'GET',
contentType: 'application/json; charset=utf-8',
data: {},
success: function (data) {
self.Names(data); //Put the response in ObservableArray
}
});
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
});
Here is the Response from the body via the ajax call:
[{"Id":1,"Name":"Brian"},{"Id":2,"Name":"Jesse"},{"Id":3,"Name":"James"}]
My HTML
<p>Current selection is <span data-bind="text:Name"></span></p>
<div class="container">
<div class="col-sm-7 well">
<form class="form-inline" action="#" method="get">
<div class="input-group col-sm-8">
<input class="form-control" placeholder="Work Section" name="q" type="text">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">Select <span class="caret"></span></button>
<ul class="dropdown-menu" data-bind="foreach: Names">
<li class="dropdown">
</li>
</ul>
<input name="category" class="category" type="hidden">
</div>
</div>
Probably because the data coming in is either not structured the same as your bindings or observables are not set/updated properly. If they aren't observed then they wont update.
I'm not 100% sure on this, but I think you have to use the observable array functions in order for observers (UI elements bound to the array or its contents) to actually get updated. Basing this on a section from the observable array documentation on the knockout site:
2.For functions that modify the contents of the array, such as push and splice, KO’s methods automatically trigger the dependency tracking
mechanism so that all registered listeners are notified of the change,
and your UI is automatically updated.
Depending on the json, one way you might fix this is clear the observable array and reload it with data elements converted to observables:
self.Names.removeAll();
var newName = null;
for (var idx = 0; idx < data.length; idx++) {
newName = data[idx];
newName.Id = ko.observable(newName.Id);
newName.Name = ko.observable(newName.Name);
self.Names.push(newName);
}
On your html. the click function is using the Name property of the selected array element as the parameter. you don't want this, you want the latest value. So change this:
click: function() {$root.Name(Name);}
to this
//notice the new parenthesis after Name.
click: function() {$root.Name(Name());}