kendo mvvm binding within a template - mvvm

I am trying to kendo mvvm bind within a template. Template variables are working but none of the MVVM stuff is.
<div id="list"></div>
<script id="template" type="text/x-kendo-template">
<div>
<button data-bind="visible: alreadyAttending, click: onClick">
Your id is ${ID}
</button>
</div>
</script>
var data = [];
data[0] = { alreadyAttending: true, ID: 1, onClick: function() { alert("Click 1"); }};
data[1] = { alreadyAttending: false, ID: 2, onClick: function() { alert("Click 2"); }};
$("#list").kendoListView({
dataSource: data,
template: kendo.template($("#template").html())
});
Fiddle available here: https://jsfiddle.net/q99ufo3c/5/
You can see the buttons are replaced with values from the data array, but visibility and click events are not wired up. I'm not sure what I'm missing. Does anyone know if this is supported?

You need to define some data attribute to the div element so that Kendo can bind it correctly to your View Model.
data-role="listview" - defines a listview component;
data-template="yourtemplateid" - defines the template to be used;
data-bind="source: dataSource" - defines what is the data
source of the listview component;
In the Javascript you need to have a Model that represents your object that will be in the data source of the list view. I created one called vm and I have chosen to use the ObservableObject extend method because it is useful when you want to create an object with default values, such as the onClick method.
Please, take a look at the Snippet below.
(function() {
var vm = kendo.data.ObservableObject.extend({
init: function(values) {
var defaultValues = {
alreadyAttending: false,
ID: null
};
kendo.data.ObservableObject.fn.init.call(this, $.extend({}, defaultValues, values));
},
onClick: function() {
alert(this.get("ID"));
}
});
var source = [
new vm({
alreadyAttending: true,
ID: 1
}),
new vm({
alreadyAttending: false,
ID: 2
})
];
var mainViewModel = kendo.observable({
dataSource: source
});
kendo.bind($("#list"), mainViewModel);
})();
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2016.2.504/js/kendo.all.min.js"></script>
</head>
<body>
<div id="list" data-role="listview" data-template="template" data-bind="source: dataSource"></div>
<script id="template" type="text/x-kendo-template">
<div>
<button data-bind="visible: alreadyAttending, click: onClick">
Your id is #:ID#
</button>
</div>
</script>
</body>
</html>

Related

How to change the state of a vue when you click the check button

I am creating a Todo Application using Vue.js, Express.js, and MongoDB.
I want to change the state of the fields that appear as v-for through the checkbox.
The code I want is to change the state of the text when the checkbox is checked and to show another element with v-if.
Here is the code I wrote: But it does not work.
If I have the wrong code, please help me.
List Component
<template>
<div class="todos">
<div v-for="todo in todos" v-bind:key="todo._id" class="todo">
<div>
<input type="checkbox" v-model="todo.done" v-on:click="completeTodo(todo)">
<del v-if="todo.done">{{todo.title}}</del>
<strong v-else>{{todo.title}}</strong>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
todos: {}
}
},
created () {
this.$http.get('/api/todos')
.then((response) => {
this.todos= response.data
})
},
methods: {
completeTodo (todo) {
todo.done = !todo.done
}
}
}
</script>
You don't need v-on:click="completeTodo(todo)". v-model is already doing the trick for you. Also, the todos in your code should be defined and instantiated as array not an object.
v-model is used for two way data binding. That means, whatever data you pass from your code will be bound to the checkbox value in this case and whenever a change is made from UI and value of checkbox is altered by user, that will be captured in v-model variable. v-model is a combo of :value prop and #change event in case of checkbox, hence, it is able to update data in both the ways.
Please refer this snippet.
var app = new Vue({
el: '#app',
data: {
todos: [
{
id: 1,
title: 'Study',
done: false
},
{
id: 2,
title: 'Work',
done: false
},
{
id: 3,
title: 'Play',
done: false
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="todos">
<div v-for="todo in todos" :key="todo.id">
<div>
<input type="checkbox" v-model="todo.done"/>
<del v-if="todo.done">{{todo.title}}</del>
<strong v-else>{{todo.title}}</strong>
</div>
</div>
</div>
</div>

Ionic 1 change text on template dynamically

I am trying to modify an ionic 1 project where I need to dynamically change the text on a tempate when it opens.
In the controller.js I have:
//Settings
$scope.settingsData = {};
$ionicModal.fromTemplateUrl('templates/settings.html', {
scope: $scope
}).then(function(modal) {
$scope.modal2 = modal;
});
$scope.closeSettings = function() {
$scope.modal2.hide();
};
$scope.settings = function() {
$scope.modal2.show();
};
$scope.doSettings = function() {
console.log('Doing Settings', $scope.settingsData);
$timeout(function() {
$scope.closeSettings();
}, 1000);
};
The template looks like this:
<ion-modal-view>
<ion-header-bar>
<h1 class="title">App Settings</h1>
<div class="buttons">
<button class="button button-clear" ng-click="closeSettings()">Close</button>
</div>
</ion-header-bar>
<div id="myid">Text in here needs to be replaced with something when it opens</div>
</div>
</ion-content>
</ion-modal-view>
So if I have a variable:
var myvar = 'Something to replace';
Then when the template opens the var above could be the text in div myid which is in the template
How can this be done?
i'll try to answer your question.
in your controller :
$scope.myvar = 'new text here !';
in your html file :
<div id="myid">{{myvar}}</div>

Is it possible to add Binding change event to a component in XML view?

I have a component in SAPUI5 where I need to listen to changes in the binding (Binding.change event). At the moment I'm adding a listener to the Binding in modelContextChange like this:
function onModelContextChange(oEvent){
var oSelect = oEvent.getSource();
var binding = oSelect.getBinding("items");
if(binding){
binding.attachChange(onDataChange, oSelect);
}
}
However this causes all kinds of weird problems, because modelContextChange could be fired multiple times. It would be better to to this in the XML view. I've tried code like this, but it doesn't work.
<Select items="{ path: 'project>/data/', change:'onDataChange' templateShareable: false">
<core:Item key="{project>Id}" text="{project>Parameter}"/>
</Select>
Any recommendations how to do this?
I just came across the problem, too. I solved it in that way:
<Select items="{
path: 'project>/data',
events: {
change: '.onDataChange'
},
templateShareable: false
}">
You can pass an events object to the binding and use the available bindings (e.g. v2.ODataListBinding). Note that you have to use a dot before your function name (.onDataChange). In your controller you can add the function:
onDataChange: function(oEvent) {
// Your implementation...
},
If I remember well from the JS Views, I think it is like this:
<Select items="{ path: 'project>/data/', events: {change:'onDataChange'}, templateShareable: false}">
This is for listening to the Model "change" events.
If you want to listen to the "change" event in the Select control, this is when the user selects a different value in the dropdown, it is like this:
<Select items="{ path: 'project>/data/', templateShareable: false}" change="onDataChange">
EDIT:
Using "modelContextChange" event.
<Select items="{ path: 'project>/data/', templateShareable: false}" modelContextChange="onDataChange">
here is an example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-libs="sap.m"></script>
<style type="text/css">
.sapMObjLTitle {
cursor: pointer;
}
</style>
<!-- XML-based view definition -->
<script id="oView" type="sapui5/xmlview">
<mvc:View height="100%" controllerName="myView.Template"
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc">
<Select change="onDataChange" items="{ path: 'project>/data', templateShareable: false}">
<core:Item key="{project>Id}" text="{project>Parameter}"/>
</Select>
</mvc:View>
</script>
</head>
<body class="sapUiBody">
<div id='content'></div>
</body>
</html>
sap.ui.define([
'jquery.sap.global',
'sap/ui/core/mvc/Controller',
'sap/ui/model/json/JSONModel'
], function(jQuery, Controller, JSONModel) {
var ListController = Controller.extend("myView.Template", {
onInit: function(oEvent) {
var oView = this.getView();
oView.setModel(new JSONModel({
data: [{
Id: "1",
Parameter: "One"
}, {
Id: "2",
Parameter: "Two"
}]
}), "project");
},
onDataChange: function() {
alert("changed")
}
});
return ListController;
});
// Instantiate the View and display
var oView = sap.ui.xmlview({
viewContent: jQuery('#oView').html()
});
oView.placeAt('content');
https://jsbin.com/zufurav/edit?html,js,output
Note: the change attribute in your XML is incorrect
I managed to get the binding change event with this set to the element that I needed by attaching a modelContextChange to the element and handling attaching the change event to the binding in there. Here's the code from the view controller:
modelContextChange: function(oEvent) {
var oElement = oEvent.getSource();
var binding = oElement.getBinding("items");
if (binding) {
//Binding change event could already be attached, detach it first, if it's there
binding.detachChange(this.onBindingChange, oSelect);
binding.attachChange(this.onBindingChange, oSelect);
// Beacause I used this inside a sap.ui.table.Treetable,
// in some cases binding changes occur without the binding change event firing.
// Manually fire the binding change just in case
// YMMV
binding.refresh(true);
}
},
onBindingChange: function() {
//The code that needs to run at binding change
//"this" is set to the correct element
//I specifically needed to add one element in front of other items in a sap.m.Select
var items = this.removeAllItems();
this.addItem(new sap.ui.core.Item({
key: 0,
text: "< Inherit >"
}));
items.forEach(function(item) {
this.addItem(item);
}, this);
}

Error submitting form data using knockout and mvc

#model NewDemoApp.Models.DemoViewModel
#{
ViewBag.Title = "Home Page";
}
#*<script src="#Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>*#
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<script src="#Url.Content("~/Scripts/knockout-3.3.0.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script type="text/javascript">
var viewModel;
var compViewModel, userViewModel;
$(document).ready(function () {
$(".wizard-step:visible").hide();
$(".wizard-step:first").show(); // show first step
$("#back-step").hide();
var result = #Html.Raw(Json.Encode(Model));
var viewModel = new DemoViewModel(result.userViewModel);
//viewModel.userViewModel.FirstName = result.userViewModel.FirstName;
//viewModel.userViewModel.LastName = result.userViewModel.LastName;
//viewModel.userViewModel.State = result.userViewModel.State;
//viewModel.userViewModel.City = result.userViewModel.City;
ko.applyBindings(viewModel);
});
var userVM = function(){
FirstName = ko.observable(),
LastName = ko.observable(),
State = ko.observable(),
City = ko.observable()
};
function DemoViewModel(data) {
var self = this;
self.userViewModel = function UserViewModel(data) {
userVM.FirstName = data.FirstName;
userVM.LastName = data.LastName;
userVM.State = data.State;
userVM.City = data.City;
}
self.Next = function () {
var $step = $(".wizard-step:visible"); // get current step
var form = $("#myFrm");
var validator = $("#myFrm").validate(); // obtain validator
var anyError = false;
$step.find("input").each(function () {
if (!validator.element(this)) { // validate every input element inside this step
anyError = true;
}
});
if (anyError)
return false; // exit if any error found
if ($step.next().hasClass("confirm")) { // is it confirmation?
$step.hide().prev(); // hide the current step
$step.next().show(); // show the next step
$("#back-step").show();
$("#next-step").hide();
//$("#myFrm").submit();
// show confirmation asynchronously
//$.post("/wizard/confirm", $("#myFrm").serialize(), function (r) {
// // inject response in confirmation step
// $(".wizard-step.confirm").html(r);
//});
}
else {
if ($step.next().hasClass("wizard-step")) { // is there any next step?
$step.hide().next().fadeIn(); // show it and hide current step
$("#back-step").show(); // recall to show backStep button
$("#next-step").show();
}
}
}
self.Back = function () {
var $step = $(".wizard-step:visible"); // get current step
if ($step.prev().hasClass("wizard-step")) { // is there any previous step?
$step.hide().prev().fadeIn(); // show it and hide current step
// disable backstep button?
if (!$step.prev().prev().hasClass("wizard-step")) {
$("#back-step").hide();
$("#next-step").show();
}
else {
$("#back-step").show();
$("#next-step").show();
}
}
}
self.SubmitForm = function (formElement) {
$.ajax({
url: '#Url.Content("~/Complaint/Save")',
type: "POST",
data: ko.toJS(self),
done: function (result) {
var newDiv = $(document.createElement("div"));
newDiv.html(result);
},
fail: function (err) {
alert(err);
},
always: function (data) {
alert(data);
}
});
}
self.loadData = function () {
$.get({
url: '#Url.Content("~/Complaint/ViewComplaint")',
done: function (data) {
debugger;
alert(data);
self.compViewModel(data);
self.userViewModel(data);
},
fail: function (err) {
debugger;
alert(err);
},
always: function (data) {
debugger;
alert(data);
}
});
}
}
</script>
<form class="form-horizontal" role="form" id="myFrm">
<div class="container">
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-6">
<div class="wizard-step">
</div>
<div class="wizard-step" >
<h3> Step 2</h3>
#Html.Partial("UserView", Model.userViewModel)
<div class="col-md-3"></div>
<div class="col-md-6">
<input type="submit" id="submitButton" class="btn btn-default btn-success" value="Submit" data-bind="click: SubmitForm" />
</div>
<div class="col-md-3"></div>
</div>
<div class="wizard-step">
<h3> Step 3</h3>
</div>
<div class="wizard-step confirm">
<h3> Final Step 4</h3>
</div>
</div>
<div class="col-md-3"></div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<input type="button" id="back-step" class="btn btn-default btn-success" value="< Back" data-bind="click: Back" />
<input type="button" id="next-step" class="btn btn-default btn-success" value="Next >" data-bind="click: Next" />
</div>
<div class="col-md-3"></div>
</div>
</div>
</form>
I am able to get the data from controller and bind it using knockout. There is a partial view that loads data from controller. But when submitting the updated data, I do not get the data that was updated, instead getting error that "FirstName" property could not be accessed from null reference. I just need to get pointers where I am going wrong especially the right way to create ViewModels and use them.
When you are submitting the form in self.SubmitForm function you are passing Json object which is converted from Knockout view model.
So make sure you are providing the data-bind attributes in all input tags properly. If you are using Razor syntax then use data_bind in Html attributes of input tags.
Check 2-way binding of KO is working fine. I can't be sure as you have not shared your partial view Razor code.
Refer-
http://knockoutjs.com/documentation/value-binding.html
In Chrome you can see what data you are submitting in Network tab of javascript developer console. The Json data that you are posting and ViewModel data structure you are expecting in controller method should match.
You can also change the parameters to expect FormCollection formCollection and check what data is coming from browser when you are posting.

Handle radio button form in Marionette js

I'm trying to construct a view in my app that will pop up polling questions in a modal dialog region. Maybe something like this for example:
What is your favorite color?
>Red
>Blue
>Green
>Yellow
>Other
Submit Vote
I've read that Marionette js doesn't support forms out of the box and that you are advised to handle on your own.
That structure above, branch and leaves (question and list of options), suggests CompositeView to me. Is that correct?
How do I trigger a model.save() to record the selection? An html form wants an action. I'm unclear on how to connect the form action to model.save().
My rough draft ItemView and CompositeView code is below. Am I in the ballpark? How should it be adjusted?
var PollOptionItemView = Marionette.ItemView.extend({
template: Handlebars.compile(
'<input type="radio" name="group{{pollNum}}" value="{{option}}">{{option}}<br>'
)
});
var PollOptionsListView = Marionette.CompositeView.extend({
template: Handlebars.compile(
//The question part
'<div id="poll">' +
'<div>{{question}}</div>' +
'</div>' +
//The list of options part
'<form name="pollQuestion" action="? what goes here ?">' +
'<div id="poll-options">' +
'</div>' +
'<input type="submit" value="Submit your vote">' +
'</form>'
),
itemView: PollOptionItemView,
appendHtml: function (compositeView, itemView, index) {
var childrenContainer = $(compositeView.$("#poll-options") || compositeView.el);
var children = childrenContainer.children();
if (children.size() === index) {
childrenContainer.append(itemView.el);
} else {
childrenContainer.children().eq(index).before(itemView.el);
}
}
});
MORE DETAILS:
My goal really is to build poll questions dynamically, meaning the questions and options are not known at runtime but rather are queried from a SQL database thereafter. If you were looking at my app I'd launch a poll on your screen via SignalR. In essence I'm telling your browser "hey, go get the contents of poll question #1 from the database and display them". My thought was that CompositeViews are best suited for this because they are data driven. The questions and corresponding options could be stored models and collections the CompositeView template could render them dynamically on demand. I have most of this wired and it looks good. My only issue seems to be the notion of what kind of template to render. A form? Or should my template just plop some radio buttons on the screen with a submit button below it and I write some javascript to try to determine what selection the user made? I'd like not to use a form at all and just use the backbone framework to handle the submission. That seems clean to me but perhaps not possible or wise? Not sure yet.
I'd use the following approach:
Create a collection of your survey questions
Create special itemviews for each type of question
In your CompositeView, choose the model itemView based on its type
Use a simple validation to see if all questions have been answered
Output an array of all questions and their results.
For an example implementation, see this fiddle: http://jsfiddle.net/Cardiff/QRdhT/
Fullscreen: http://jsfiddle.net/Cardiff/QRdhT/embedded/result/
Note:
Try it without answering all questions to see the validation at work
Check your console on success to view the results
The code
// Define data
var surveyData = [{
id: 1,
type: 'multiplechoice',
question: 'What color do you like?',
options: ["Red", "Green", "Insanely blue", "Yellow?"],
result: null,
validationmsg: "Please choose a color."
}, {
id: 2,
type: 'openquestion',
question: 'What food do you like?',
options: null,
result: null,
validationmsg: "Please explain what food you like."
}, {
id: 3,
type: 'checkbox',
question: 'What movie genres do you prefer?',
options: ["Comedy", "Action", "Awesome", "Adventure", "1D"],
result: null,
validationmsg: "Please choose at least one movie genre."
}];
// Setup models
var questionModel = Backbone.Model.extend({
defaults: {
type: null,
question: "",
options: null,
result: null,
validationmsg: "Please fill in this question."
},
validate: function () {
// Check if a result has been set, if not, invalidate
if (!this.get('result')) {
return false;
}
return true;
}
});
// Setup collection
var surveyCollection = Backbone.Collection.extend({
model: questionModel
});
var surveyCollectionInstance = new surveyCollection(surveyData);
console.log(surveyCollectionInstance);
// Define the ItemViews
/// Base itemView
var baseSurveyItemView = Marionette.ItemView.extend({
ui: {
warningmsg: '.warningmsg',
panel: '.panel'
},
events: {
'change': 'storeResult'
},
modelEvents: {
'showInvalidMessage': 'showInvalidMessage',
'hideInvalidMessage': 'hideInvalidMessage'
},
showInvalidMessage: function() {
// Show message
this.ui.warningmsg.show();
// Add warning class
this.ui.panel.addClass('panel-warningborder');
},
hideInvalidMessage: function() {
// Hide message
this.ui.warningmsg.hide();
// Remove warning class
this.ui.panel.removeClass('panel-warningborder');
}
});
/// Specific views
var multipleChoiceItemView = baseSurveyItemView.extend({
template: "#view-multiplechoice",
storeResult: function() {
var value = this.$el.find("input[type='radio']:checked").val();
this.model.set('result', value);
}
});
var openQuestionItemView = baseSurveyItemView.extend({
template: "#view-openquestion",
storeResult: function() {
var value = this.$el.find("textarea").val();
this.model.set('result', value);
}
});
var checkBoxItemView = baseSurveyItemView.extend({
template: "#view-checkbox",
storeResult: function() {
var value = $("input[type='checkbox']:checked").map(function(){
return $(this).val();
}).get();
this.model.set('result', (_.isEmpty(value)) ? null : value);
}
});
// Define a CompositeView
var surveyCompositeView = Marionette.CompositeView.extend({
template: "#survey",
ui: {
submitbutton: '.btn-primary'
},
events: {
'click #ui.submitbutton': 'submitSurvey'
},
itemViewContainer: ".questions",
itemViews: {
multiplechoice: multipleChoiceItemView,
openquestion: openQuestionItemView,
checkbox: checkBoxItemView
},
getItemView: function (item) {
// Get the view key for this item
var viewId = item.get('type');
// Get all defined views for this CompositeView
var itemViewObject = Marionette.getOption(this, "itemViews");
// Get correct view using given key
var itemView = itemViewObject[viewId];
if (!itemView) {
throwError("An `itemView` must be specified", "NoItemViewError");
}
return itemView;
},
submitSurvey: function() {
// Check if there are errors
var hasErrors = false;
_.each(this.collection.models, function(m) {
// Validate model
var modelValid = m.validate();
// If it's invalid, trigger event on model
if (!modelValid) {
m.trigger('showInvalidMessage');
hasErrors = true;
}
else {
m.trigger('hideInvalidMessage');
}
});
// Check to see if it has errors, if so, raise message, otherwise output.
if (hasErrors) {
alert('You haven\'t answered all questions yet, please check.');
}
else {
// No errors, parse results and log to console
var surveyResult = _.map(this.collection.models, function(m) {
return {
id: m.get('id'),
result: m.get('result')
}
});
// Log to console
alert('Success! Check your console for the results');
console.log(surveyResult);
// Close the survey view
rm.get('container').close();
}
}
});
// Create a region
var rm = new Marionette.RegionManager();
rm.addRegion("container", "#container");
// Create instance of composite view
var movieCompViewInstance = new surveyCompositeView({
collection: surveyCollectionInstance
});
// Show the survey
rm.get('container').show(movieCompViewInstance);
Templates
<script type="text/html" id="survey">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" > A cool survey regarding your life </h3>
</div>
<div class="panel-body">
<div class="questions"></div>
<div class="submitbutton">
<button type="button" class="btn btn-primary">Submit survey!</button>
</div>
</div>
</div >
</script>
<script type="text/template" id="view-multiplechoice">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<% _.each( options, function( option, index ){ %>
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="<%= index %>" value="<%= option %>"> <%= option %>
</label>
</div>
<% }); %>
</div>
</div>
</script>
<script type="text/template" id="view-openquestion">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<textarea class="form-control" rows="3"></textarea>
</div>
</div >
</script>
<script type="text/template" id="view-checkbox">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<% _.each( options, function( option, index ){ %>
<div class="checkbox">
<label>
<input type="checkbox" value="<%= option %>"> <%= option %>
</label>
</div>
<% }); %>
</div>
</div>
</script>
<div id="container"></div>
Update: Added handlebars example
Jsfiddle using handlebars: http://jsfiddle.net/Cardiff/YrEP8/