I have this code...
$(function() {
var scheduler = $("#scheduler").kendoScheduler({
date: new Date("2015/1/1"),
views: [
{
type: "agenda",
eventTemplate: $("#event-template").html()
},
],
dataSource: [
{
id: 1,
start: new Date("2015/1/1 08:00 AM"),
end: new Date("2015/1/1 09:00 AM"),
title: "Test",
atendees: [1]
}
],
resources: [
{
field: "atendees",
dataSource: [
{ value: 1, text: "Jim" },
],
multiple: true
}
]
}).data("kendoScheduler");
$("#scheduler").on("click", ".edit-event",function() {
var dataItem = scheduler.occurrenceByUid($(this).data("uid"));
scheduler.editEvent(dataItem);
});
});
which works fine, but I am moving over to an MVVM approach can anyone help me get my new MVVM code to accept a template for my agenda view please. Code I have is
<script id="event-template" type="text/x-kendo-template">
<div class="edit-event" data-uid="#=uid#">
<div><b>Job</b>: #: title #</div>
<div>
<b>Staff:</b>
# for (var i = 0; i < resources.length; i++) { #
#: resources[i].text #
# } #
</div>
</div>
</script>
<div id="topper">
<div class="demo-section k-header wide">
<div>
<div data-role="scheduler" data-views="['agenda','month']" data-bind="source: tasks, visible: isVisible, events: { save: onSave }" data-template="event-template"></div>
</div>
</div>
</div>
Which does not work - doesn't crash, it just doesn't load the template I am also aware that this approach may use the template for all views not just agenda. Any help would be most appreciated.
To achieve the same behavior in MVVM you should define the view as object inside the "data-views" option:
<div data-role="scheduler"
data-views="['agenda',{ type: 'agenda', eventTemplate: $('\#event-template').html() }]"
data-bind="source: tasks, visible: isVisible, events: { save: onSave }"
data-template="event-template"></div>
Related
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>
I'm making a list of items with v-for loop. I have some API data from server.
items: [
{
foo: 'something',
number: 1
},
{
foo: 'anything',
number: 2
}
]
and my template is:
<div v-for(item,index) in items #click=toggleActive>
{{ item.foo }}
{{ item.number }}
</div>
JS:
methods: {
toggleActive() {
//
}
}
How can i toggle active class with :class={active : something} ?
P.S I don't have boolean value in items
You can try to implement something like:
<div
v-for="(item, index) in items"
v-bind:key="item.id" // or alternativelly use `index`.
v-bind:class={'active': activeItem[item.id]}
#click="toggleActive(item)"
>
JS:
data: () => ({
activeItem: {},
}),
methods: {
toggleActive(item) {
if (this.activeItem[item.id]) {
this.removeActiveItem(item);
return;
}
this.addActiveItem(item);
},
addActiveItem(item) {
this.activeItem = Object.assign({},
this.activeItem,
[item.id]: item,
);
},
removeActiveItem(item) {
delete this.activeItem[item.id];
this.activeItem = Object.assign({}, this.activeItem);
},
}
I had the same issue and while it isn't easy to find a whole lot of useful information it is relatively simple to implement. I have a list of stores that map to a sort of tag cloud of clickable buttons. When one of them is clicked the "added" class is added to the link. The markup:
<div class="col-sm-10">
{{ store.name }}
</div>
And the associated script (TypeScript in this case). toggleAdd adds or removes the store id from selectedStoreIds and the class is updated automatically:
new Vue({
el: "#productPage",
data: {
stores: [] as StoreModel[],
selectedStoreIds: [] as string[],
},
methods: {
toggleAdd(store: StoreModel) {
let idx = this.selectedStoreIds.indexOf(store.id);
if (idx !== -1) {
this.selectedStoreIds.splice(idx, 1);
} else {
this.selectedStoreIds.push(store.id);
}
},
async mounted () {
this.stores = await this.getStores(); // ajax request to retrieve stores from server
}
});
Marlon Barcarol's answer helped a lot to resolve this for me.
It can be done in 2 steps.
1) Create v-for loop in parent component, like
<myComponent v-for="item in itemsList"/>
data() {
return {
itemsList: ['itemOne', 'itemTwo', 'itemThree']
}
}
2) Create child myComponent itself with all necessary logic
<div :class="someClass" #click="toggleClass"></div>
data(){
return {
someClass: "classOne"
}
},
methods: {
toggleClass() {
this.someClass = "classTwo";
}
}
This way all elements in v-for loop will have separate logic, not concerning sibling elements
I was working on a project and I had the same requirement, here is the code:
You can ignore CSS and pick the vue logic :)
new Vue({
el: '#app',
data: {
items: [{ title: 'Finance', isActive: false }, { title: 'Advertisement', isActive: false }, { title: 'Marketing', isActive: false }],
},
})
body{background:#161616}.p-wrap{color:#bdbdbd;width:320px;background:#161616;min-height:500px;border:1px solid #ccc;padding:15px}.angle-down svg{width:20px;height:20px}.p-card.is-open .angle-down svg{transform:rotate(180deg)}.c-card,.p-card{background:#2f2f2f;padding:10px;border-bottom:1px solid #666}.c-card{height:90px}.c-card:first-child,.p-card:first-child{border-radius:8px 8px 0 0}.c-card:first-child{margin-top:10px}.c-card:last-child,.p-card:last-child{border-radius:0 0 8px 8px;border-bottom:none}.p-title .avatar{background-color:#8d6e92;width:40px;height:40px;border-radius:50%}.p-card.is-open .p-title .avatar{width:20px;height:20px}.p-card.is-open{padding:20px 0;background-color:transparent}.p-card.is-open:first-child{padding:10px 0 20px}.p-card.is-open:last-child{padding:20px 0 0}.p-body{display:none}.p-card.is-open .p-body{display:block}.sec-title{font-size:12px;margin-bottom:10px}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<div id="app" class="p-5">
<div class="p-wrap mx-auto">
<div class="sec-title">NEED TO ADD SECTION TITLE HERE</div>
<div>
<div v-for="(item, index) in items" v-bind:key="index" class="p-card" v-bind:class="{'is-open': item.isActive}"
v-on:click="item.isActive = !item.isActive">
<div class="row p-title align-items-center">
<div class="col-auto">
<div class="avatar"></div>
</div>
<div class="col pl-0">
<div class="title">{{item.title}}</div>
</div>
<div class="col-auto">
<div class="angle-down">
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="angle-down" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"
class="svg-inline--fa fa-angle-down fa-w-10 fa-3x">
<path fill="currentColor"
d="M151.5 347.8L3.5 201c-4.7-4.7-4.7-12.3 0-17l19.8-19.8c4.7-4.7 12.3-4.7 17 0L160 282.7l119.7-118.5c4.7-4.7 12.3-4.7 17 0l19.8 19.8c4.7 4.7 4.7 12.3 0 17l-148 146.8c-4.7 4.7-12.3 4.7-17 0z"
class=""></path>
</svg>
</div>
</div>
</div>
<div class="p-body">
<div class="c-card"></div>
<div class="c-card"></div>
<div class="c-card"></div>
</div>
</div>
</div>
</div>
</div>
I am using struts2 autocomplete in a form. I will do a form submit after I choose all fields through autocomplete on various text boxes. Now, I don't want to allow user to type a random input in the auto complete text box and allow a submit. How to tackle this ?
EDIT:
<form class="form-horizontal" name="createproduct" role="form">
<div class="form-group">
<label for="density" class="col-sm-3 control-label">Select
Density</label>
<div class="col-sm-9">
<sj:autocompleter size="30" list="descArray" name="desc"
id="density" placeholder="Density" class="form-control"></sj:autocompleter>
</div>
</div>
<div class="form-group">
<label for="density" class="col-sm-3 control-label">Select
Material</label>
<div class="col-sm-9">
<sj:autocompleter size="30" list="matArray" name="mat" id="mat"
placeholder="Material" class="form-control"></sj:autocompleter>
</div>
</div>
..... //// more form elements go here ...... /////
Try Select2 jquery plugin.You can either fetch all the details and initialize the plugin or can fetch info on demand.
Fetching info based on input;
$("#material").select2({
tags: false,
minimumInputLength: 1, // Minimum Input length for search request
ajax: {
url:'actionName',
dataType:'json',
data:function(params){
return{
materialSearchTerm: params.term, // search term
page: params.page
};
},
processResults: function (data, page) {
var jsonResult = JSON.parse(data);
return {
results: jsonResult.result
};
}
}
});
actionName should return select input as JSON.
eg;- {result:[{ id: 0, text: 'enhancement' }, { id: 1, text: 'bug' }, { id: 2, text: 'duplicate' }, { id: 3, text: 'invalid' }, { id: 4, text: 'wontfix' }];}
getting into AmpersandJs, and having a big showstopper by not figuring out why my sub-view-form isn't rendering it's markup.
My MainView.render, works as should:
render: function() {
BaseView.prototype.render.apply(this, arguments);
this.collectionView = this.renderCollection(this.collection, HSEventView, '.item-container');
this.renderSubview(new HSEventEditView({
action: 'create'
}), '.create-event');
return this;
},
Next, my SubView.render (HSEventEditView):
render: function () {
this.renderWithTemplate();
this.form = new EditForm({
el: this.query('.edit-form'),
submitCallback: function (data) {
debug('submit', data);
}
});
this.registerSubview(this.form);
debug('render.form.el', this.form.el)
}
And finally my FormView:
module.exports = FormView.extend({
initialize: function() {
debug('initialize', this.el)
},
autoRender: true,
fields: function () {
debug('fields!')
var fields = [
new InputView({
label: 'Name',
name: 'name',
value: utils.getDotted(this, 'model.name'),
placeholder: 'Name',
parent: this
})
];
debug('fields', fields)
}
});
The MainView and SubView renders fine, but the 'div.edit-form' DOM-node, where the form markup should be, is empty.
I have tried all variations of including a subview I could dig up, but obviously I am blind to something.
Thanks!
PS! Here is the rendered markup:
<section class="page">
<h2>Events collection</h2>
<hr>
<div class="tools">(Tools comming...)</div>
<hr>
<div class="item-container events">
<div class="item event">
<h3>Event: <span data-hook="name">Event 1</span></h3>
</div>
</div>
<hr>
<div class="create-event">
<div class="item event">
<h3>Create event: <span data-hook="name"></span></h3>
<div class="edit-form"></div>
</div>
</div>
</section>
Since fields is a function, you need to return the fields. In your Formview:
fields: function () {
debug('fields!')
var fields = [
new InputView({
label: 'Name',
name: 'name',
value: this.model.name,
placeholder: 'Name',
parent: this
})
];
debug('fields', fields)
// need to return the fields you've declared
return fields;
}
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/