EmberJS: Observer Not Being Triggered on Computed Property - forms

I am building a handelbars helper that renders a checkbox group. My goal is to display a checkbox group with something like this and get two-way binding on selectedOptions:
{{form-checkboxGroup options=allOptions selectedOptions=selectedOptions}}
I've used this pattern successfully with other form components and it's a big win. I'm able to render my allOptions and selectedOptions values as a checkbox group, but it's the two-way binding that's tripping me up. Any idea what I'm missing?
By the way, I'm using ember-cli, but that doesn't affect anything relating to this issue.
Here's my setup:
Handlebars Helper: helpers/form-checkbox-group.js
The sole purpose of this file is to link the Handelbars expression {{form-checkboxGroup}} to the view and template below.
import FormCheckboxGroupView from 'my-app/views/util/form/form-checkbox-group';
export default Ember.Handlebars.makeBoundHelper(function( options ) {
return Ember.Handlebars.helpers.view.call(this, FormCheckboxGroupView, options);
});
CheckboxGroup Handlebars Template: templates/util/form/form-checkbox-group.hbs
...
{{#each user in view.combinedOptions}}
{{input type="checkbox" name="view.fieldName" checked=user.checked }} {{user.name}}
{{/each}}
...
CheckboxGroup View: views/util/form/form-checkbox-group.js
...
export default FormCheckboxGroupView = Ember.View.extend( FormFieldMixin, {
templateName: 'util/form/form-checkbox-group',
selectedOptions: function() {
console.log("When triggered this could update view.selectedOptions");
}.observes('view.combinedOptions.#each.checked'),
// combines the "options" and "selected options" into a single array of "combinedOptions" explicitly indicating what's checked
combinedOptions: function() {
...
// sample result of combinedOptions:
// { name: "Johnny Five", id: "12", checked: true }
return combinedOptions;
}.property('view.options', 'view.selectedOptions')
});
And finally, to actually use my Handlebars helper, here's the consuming page's template and corresponding controller:
Consuming Page: templates/my-page.hbs
{{form-checkboxGroup options=allUsersArray selectedOptions=selectedUsersArray fieldName="selectedProvidersArray" }}
Backing Controller for Consuming Page: controllers/my-page.js
export default MyPageController = Ember.Controller.extend( FormMixin, {
allUsersArray: [
{ name: 'Bill Huxtable', id: 'billy' },
{ name: 'Samantha Jones', id: 'jones' },
{ name: 'Tony Pepperoni', id: 'tonyp' },
{ name: 'Ridonk Youliss', id: 'silly' }
],
selectedUsersArray: [
{ name: 'Tony Pepperoni', id: 'tonyp' },
{ name: 'Ridonk Youliss', id: 'silly' }
],
...
});
So, all of this successfully renders the checkbox group nicely, but my efforts to capture the fact that a checkbox has been newly selected by using observes("view.combinedOptions.#each.checked') is not working.
Any idea on how I can this up for two-way binding? Thanks in advance for assistance!

No jsbin so I'm flying blind, but try this:
selectedOptions: function() {
console.log("When triggered this could update view.selectedOptions");
}.observes('combinedOptions.#each.checked')
view.property is how you access view from template. You don't need that from the view itself (unless you have view property on your view).

Related

Dynamically changing the language of columns labels in VueJS

I implemented a multilanguage support for the website. Using VueJS and VueI18n. There are 3 pages - home, registers and messages. The problem is in messages, where there is a dynamically rendered table - vue-good-table. While being on this page(with the table) if I click on the buttons for changing languages, everywhere the languages is being changed dynamically, but not the labels and placeholders of the table. If I go to one of the other pages and comeback to the table, the labels and placeholders are updated correctly. Can you help me make it change while I am on the same page?
I was wondering if beforeMount() would help in this situation?
main.js
import VueI18n from 'vue-i18n'
import {messages} from './locales/bg_en_messages'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'bg', // set locale
fallbackLocale: 'bg',
messages // set locale messages
});
Vue.prototype.$locale = {
change (lang) {
i18n.locale = lang
},
current () {
return i18n.locale
}
};
Messages.vue:
<vue-good-table
:columns="columns"
:rows="items"
:paginate="true"
:lineNumbers="true">
</vue-good-table>
<script type="text/javascript">
export default {
data(){
return {
items:[],
columns: [
{
label: this.$t("columns.date"),
field: 'changeddate',
type: 'String',
filterable: true,
placeholder: this.$t("columns.date")
},
{
label: this.$t("columns.userChange"),
field: 'userchange',
type: 'String',
filterable: true,
placeholder: this.$t("columns.userChange")
}
]
}
}
}
App.vue
<div style="padding: 10px; width: 99%;">
<ui-button #click="changeLang('bg')">
<img src="../src/assets/images/skin/Flag1.png" v-bind:alt="home" height="15" width="25"/>
</ui-button>
<ui-button #click="changeLang('en')">
<img src="../src/assets/images/skin/Flag2.png" v-bind:alt="home" height="15" width="25"/>
</ui-button>
</div>
<script>
export default {
name: 'Localization',
methods: {
changeLang: function(newLang){
this.$locale.change(newLang)
}
}
}
</script>
The reason is that the data that's changing is nested inside an object itself and your template only listens to changes to that parent object and not for its children (your language data). So even if that changes, your view wont notice it. If you use data from your translation directly in the template using the template syntax, the data will re-render automatically because that is not nested(that's why it probably works everywhere else).
Now of course you can't do that in your table's case, because your table component accepts nested data only, so the workaround would be to use a computed property for your columns, instead of putting them into your component's data. This way all changes will be mirrored to your component.
Simply change your component like that and you should be good to go:
export default {
data(){
return {
items:[]
}
},
computed: {
columns () {
return [
{
label: this.$t("columns.date"),
field: 'changeddate',
type: 'String',
filterable: true,
placeholder: this.$t("columns.date")
},
{
label: this.$t("columns.userChange"),
field: 'userchange',
type: 'String',
filterable: true,
placeholder: this.$t("columns.userChange")
}
]
}
}
}

Refresh custom control in sapui5 when model change

I've a custom control which have multiple properties inserted in Detail View page. I've binded data with these properties. Scenario is I've two pages one is list view and then detail view. I've to navBack from detail page and select diff product from main page.Detail view page show diff products detail according to selected product. everything works fine. but problem is that my custom control doesn't update values and other page have updated values.
<custom:product topic="" subTopic="{product>name}" id="productDetial"></custom:product>
I've used one methond this.getView().byId("productDetail").rerender(); but it doesn't update my Inner HTML of control.
the control code. might be some typos error.as I've changed some variables name and remove unwanted code. the purpose is to show the methods which I've used and how I did
sap.ui.define([
"sap/m/Label",
"sap/m/Button",
"sap/m/CustomTile"
], function(Label, Button, CustomTile) {
"use strict";
jQuery.sap.declare("testProduct.control.product");
return CustomTile.extend("testProduct.control.product", {
metadata: { // the Control API
properties: {
name: {
type: "string",
defaultValue: "--"
},
subTopic: {
type: "string",
defaultValue: "--"
}
}
},
init: function() {
},
rerender: function(oRM, oControl) {
},
renderer: function(oRM, oControl) {
oRM.write('<div class=" sapMTile customTileCourseDetail">');
oRM.write('<div class="leftTileYourScore">');
if (oControl.getSubTopic() !== "" && oControl.getSubTopic() !== undefined) {
oRM.writeEscaped(oControl.getSubTopic());
} else {
oRM.write(" ");
}
oRM.write('</div>');
oRM.write('</div>
}
});
});
Yo just need to add a setter function in you control. When the binding is refreshed/changes, UI5 will trigger a setter method specific to the property. So in you case for the property subTopic it expects a method setSubTopic. This method should define you own logic to update said property in the UI layer according to your needs.
Here is part of the code you need to add, you will also have to tweak the initial rendering logic a bit.
renderer: function (oRM, oControl) {
//oRM.write('<div class=" sapMTile customTileCourseDetail">');
oRM.write("<div")
oRM.writeControlData(oControl);
oRM.addClass("sapMTile customTileCourseDetail");
oRM.writeClasses();
oRM.write(">");
oRM.write('<div class="leftTileYourScore">');
if (oControl.getSubTopic() !== "" && oControl.getSubTopic() !== undefined) {
oRM.writeEscaped(oControl.getSubTopic());
} else {
oRM.write(" ");
}
oRM.write('</div>');
oRM.write('</div>');
},
setSubTopic: function(sText){
this.setProperty("subTopic", sText, true);
$("#"+this.sId+" .leftTileYourScore").html(sText);
}

full transition after updating query parameters in ember

I have the following route that run a report for specific date range:
export default Ember.Route.extend({
model: function(params){
return this.store.query('report' {
"report":"my_report",
"from":params.startDate,
"to":params.endDate
});
},
setupController: function(controller, model) {
this._super(controller, model);
// change format model and save it in rows
controller.set('model', rows);
}
});
Now, my controller is as follow:
export default Ember.Controller.extend({
queryParams:["startDate","endDate"],
actions:{
processReport:function(from,to){
this.transitionToRoute('reports',{
queryParams :{
"startDate":from,
"endDate":to,
"refreshModel": true
}
});
}
},
to:"",
from:""
});
The template is as follow:
From {{bootstrap-datepicker value=to}}
To {{bootstrap-datepicker value=from}}
<button {{action "processReport" from to}}>Process Report</button>
So, when I click the button the url changed and console show:
Attempting transition to reports ember.debug.js:52602
Transitioned into 'reports' ember.debug.js:27426
but the page remains same. How do I fully transition to the page?
You should add to your route the queryParams configuration to make a full transition
export default Ember.Route.extend({
queryParams: {
startDate: {
refreshModel: true
},
endDate: {
refreshModel: true
}
},
...
And on the controller you'll need to just update the values "startDate" and "endDate" in the custom action:
Export default Ember.Controller.extend({
queryParams:["startDate","endDate"],
actions:{
processReport:function(){
this.set('startDate', this.get('from'));
this.set('endDate', this.get('to'));
}
},
to:"",
from:""
});
This has been explained on the guides:
https://guides.emberjs.com/v2.11.0/routing/query-params/#toc_opting-into-a-full-transition
I figured it out on my own by reading the updated API on query-params in the following link:
https://guides.emberjs.com/v2.3.0/routing/query-params/

Is it possible to hide sap.m.input "description" property value

I'm using the description field to hold an value that I don't want to be displayed, is it possible to set this property to visible:false or set to width to 0?
new sap.m.Input("idAltDistInput"+refDocID+sequenceID, {value:"{AltDistrictDesc}",
description: { path : 'AltDistrictID' }
:
visible : false doesn't seem to work.
Yes you can by adding StyleClass.
sap.m.Input("id",{
//Properties
}).addStyleClass("InputDescripTionHidden");
Add following css
.InputDescripTionHidden>span{
display:none
}
Your comment above suggests that you want to store some hidden value, for later use.
Rather than "hijack" (in the nicest sense of the word) another property, you should consider using Custom Data, which is designed for this sort of thing. Here's an example.
new sap.m.List({
mode: "SingleSelectMaster",
items: {
path: "/records",
template: new sap.m.InputListItem({
label: "District",
content: new sap.m.Input({
value: "{AltDistrictDesc}",
customData: [new sap.ui.core.CustomData({
key: "DistrictID",
value: "{AltDistrictID}"
})]
})
})
},
select: function(oEvent) {
var id = oEvent.getParameter("listItem")
.getContent()[0] // the Input control
.getCustomData()[0] // the only Custom Data
.getValue();
alert("Selected District ID : " + id);
}
})
.setModel(new sap.ui.model.json.JSONModel({
records: [{
AltDistrictID: "D1",
AltDistrictDesc: "District 1"
}, {
AltDistrictID: "D2",
AltDistrictDesc: "District 2"
}]
}))
.placeAt("content");
<script src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-libs="sap.m" data-sap-ui-theme="sap_bluecrystal"></script>
<div class="sapUiBody" id="content"></div>
(Note that for simplicity, I'm just grabbing the first content and the first custom data within that content in the select listener. You will want to do this slightly more precisely in real code.)

Using KoGrid in HotTowel template

I am trying to use KoGrid in a HTML view within the HotTowel SPA template. I created a simple view:
<section>
<h2 class="page-title" data-bind="text: title"></h2>
<div class="gridStyle" data-bind="koGrid: gridOptions"></div>
</section>
and added the model data in the JS:
define(['services/logger'], function (logger) {
var vm = {
activate: activate,
title: 'My Grid'
};
return vm;
//#region Internal Methods
function activate() {
var self = this;
this.myData = ko.observableArray([{ name: "Moroni", age: 50 },
{ name: "Tiancum", age: 43 },
{ name: "Jacob", age: 27 },
{ name: "Nephi", age: 29 },
{ name: "Enos", age: 34 }]);
this.gridOptions = { data: self.myData };
return true;
}
//#endregion
});
The grid is on the page, but the styling seems to be rendering widths and positions completely wrong so that columns are on top of each other and most data is not visibly correct. The KoGrid.css file is being referenced correctly.
Thanks for any help.
The core of the problem is that "when KOGrid does its binding in Durandal/HotTowel, the KOGrid element is not yet part of the DOM". You need to ensure that KOGrid does not do its binding until after the view is attached. This can be achieved as follows:
1) Add a new observable to the viewmodel to hold a boolean value for whether the view has been attached or not by durandal:
isAttachedToView = ko.observable(false)
and expose it
isAttachedToView: isAttachedToView
2) Up date it to be true when the viewAttached function callback is invoked:
function viewAttached() {
isAttachedToView(true);
return true;
}
3) Surround your HTML with a ko if statement to ensure that bit of HTML is not evaluated (i.e. kogrid does not do its binding) until after the view is attached:
<!-- ko if: isAttachedToView() -->
<div data-bind="koGrid: { data: ...
<!-- /ko -->
4) Reset isAttachedToView to be false on deactivating view
function deactivate() {
isAttachedToView(false);
}
And expose this:
deactivate: deactivate
You have probably already figured this one out but was also faced with the same problem today. A quick look at the chrome inspector told me that koGrid dimensional properties have not registered correctly and this tells me its a timing issue. I found an answered question relating to the same problem here.
I did try this solution but there is still some work to do to make it play ball nicely. I have decided to give koGrid a miss since I don't really want all it's functionality anywayz :)