I am trying to use a template as shown below, the outcome is a view with all elements from the template on one line, even though i am using to separate the elements. Why does this not display properly? It seems that no matter what styling i do it still ends up a single line view.
UPDATE
The culprit is the kendo style sheet - kendo.mobile.all.min.css -
So the new question for a kendo expert is why does kendo handle input fields differently when they appear in a listview via a template than when they appear outside of a template?
An input field outside of a listview template gets this class
.km-ios .km-list input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):not([type="checkbox"]):not([type="radio"]):not(.k-input):not(.k-button), .km-ios .km-list select:not([multiple]), .km-ios .km-list .k-dropdown-wrap, .km-ios .km-list textarea
Which results in no odd styling rules :) Normal text field view
An input field inside of the template gets this class
.km-root input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):not([type="checkbox"]):not([type="radio"]):not(.k-input):not(.k-button), .km-root select:not([multiple]), .km-root .k-dropdown, .km-root textarea
which results in these rules being applied to it (making the field sit in a wierd spot and loose all normal field stlying ie border background etc.) Im not 100% sure which wrapper is causing this
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
font-size: 1.1rem;
color: #385487;
min-width: 6em;
border: 0;
padding: .4em;
outline: 0;
background:
transparent;
My work around is to give any text fields inside listview templates the class="k-input" which obviously excludes them from the above css -
<script src="kendo/js/jquery.min.js"></script>
<script src="kendo/js/kendo.mobile.min.js"></script>
<link href="kendo/styles/kendo.mobile.all.min.css" rel="stylesheet" />
<!-- eventDetail view -------------------------------------------------------------------------------------------------->
<div data-role="view" id="view-eventDetail" data-show="getEventDetailData" data-title="eventDetail">
<header data-role="header">
<div data-role="navbar">
<span data-role="view-title"></span>
<a data-align="right" data-role="button" class="nav-button" href="#view-myEvents">Back</a>
</div>
</header>
<form id="updateEventForm">
<div id="updateEvent">
<div id="eventDetail"></div>
<p>
<input type="button" id="eventUpdateCancelButton" style="width:30%" data-role="button" data-min="true" value="Back" />
<input type="submit" id="eventUpdateSaveButton" style="width:30%" data-role="button" data-min="true" value="Save" />
</p>
<div id="eventResult"></div>
</div>
</form>
</div>
<script id="eventDetail-template" type="text/x-kendo-template">
<p>
<input name="event_type" id="event_type" data-min="true" type="text" value="#= type #" />
</p>
<p>
<input name="event_loc" id="event_loc" data-min="true" type="text" value="#= type #" />
</p>
<p>
<input name="event_date_time" id="event_date_time" data-min="true" type="datetime" value="#= stamp #" />
</p>
<p>
Share this
<input data-role="switch" id="event_share" data-min="true" checked="checked" value="#= share #"/>
</p>
<input name="userID" id="userID" type="hidden" value="#= user_id #" />
<input name="eventID" id="eventID" type="hidden" value="#= event_id #" />
</script>
<script>
function getEventDetailData(e) {
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: "http://localhost/mpt/website/api/event_details.php",
dataType: "jsonp",
type: "GET",
data: { userID: e.view.params.user_id, eventID: e.view.params.event_id },
cache: false
},
parameterMap: function(options) {
return {
userID: options.userID,
eventID: options.eventID
};
}
},
schema: { // describe the result format
data: "results" // the data which the data source will be bound to is in the "results" field
}
});
console.log(e);
$("#eventDetail").kendoMobileListView({
dataSource: dataSource,
template: kendo.template($("#eventDetail-template").html())
}).data("kendoMobileListView");
}
//update event
function sendUpdateEvent() {
var siteURI = "http://localhost/mpt/website/api/update_event.php?";
app.showLoading();
var user_id = $('#userID').val();
var event_id = $('#eventID').val();
var event_type = $('#event_type').val();
var event_loc = $('#event_loc').val();
var event_date_time = $('#event_date_time').val();
var event_share = $('#event_share').val();
var formVals = 'eventID=' + event_id + '&userID=' + user_id + '&event_type=' + event_type + '&event_loc=' + event_loc + '&event_date_time=' + event_date_time + '&event_share=' + event_share;
var fullURI = siteURI + formVals;
$.ajax({
url: fullURI, dataType: 'json', success: function (data) {
$('#eventResult').html(data.results);
app.hideLoading();
app.navigate("#view-myEvents");
}
});
}
$('#eventUpdateCancelButton').click(function () {
app.navigate("#view-myEvents");
});
$('#eventUpdateSaveButton').click(function () {
sendUpdateEvent();
});
$('#updateEventForm').submit(function () {
sendUpdateEvent();
return false;
});
</script>
ListView widgets are supposed to be applied to <ul> elements.
Try changing:
<div id="eventDetail"></div>
to:
<ul id="eventDetail"></ul>
Also with this bit of code:
$("#eventDetail").kendoMobileListView({
dataSource: dataSource,
template: kendo.template($("#eventDetail-template").html())
}).data("kendoMobileListView");
The .data() call on the end isn't doing anything here and can be removed, and also you can pass just the text string as the template. You don't need to call kendo.template() yourself. So you can change that to just:
$("#eventDetail").kendoMobileListView({
dataSource: dataSource,
template: $("#eventDetail-template").html()
});
Related
So I am working with Knockout inside Magento 2.3.4, and I am setting a custom observable value on initialize, and then I am trying to access that observable and change the value inside a function. Every time I try, I keep getting "it is not a function", and it won't let me either retrieve and read the current observable value, or set a new one. When I try to run .isObservable() on it it comes up as false. I have looked through various examples of how to do it and tried all of them and none of them work. Currently my knockout JS form looks like this:
define([
'jquery',
'uiComponent',
'ko'
], function($, Component, ko) {
'use strict';
return Component.extend({
defaults: {
template: 'Shmoop_Cms/career-form'
},
progressText: ko.observable(false),
initialize: function() {
var self = this;
this._super();
this.progressText('1 of 15 questions completed');
return this;
},
showNext: function() {
let dataIndex = parseInt($('.quiz-card.show').attr('data-index')) + 1;
alert(ko.isObservable(this.progressText));
alert(this.progressText());
this.progressText(dataIndex + ' of 15 questions completed');
}
});
});
I am able to set the progressText value initially inside that initialize function without issue, and it recognizes there that it is an observable. Why does it say it's not an observable inside by "showNext" function?
FYI I have also tried adding "var self = this" inside my function, I have also tried "self.progressText()" instead of "this.progressText()", nothing worked.
Please help.
Edited: By the way, my template looks like this:
<div class="career-quiz-wrapper">
<!-- ko if: quizQuestions -->
<form class="career-quiz-form" data-bind="foreach: quizQuestions">
<div class="quiz-card" data-bind="attr: {'data-question-uuid': uuid, 'data-index': index }, css: index == 0 ? 'show' : 'hide'">
<h2 class="display-heading title">How important is it to you to...</h2>
<div class="lead main-text" data-bind="html: question"></div>
<div class="choices">
<div class="row-bar">
<input type="range" data-anchor="range" class="custom-range" min="0" max="100" value="50" autocomplete="off" data-bind="event: { change: $parent.adjustRangeSlider }">
</div>
<div class="row-options">
<div class="option-section" data-bind="foreach: choiceUUIDs">
<div class="choice-option-button">
<a class="option-button" data-bind="click: $parents[1].choiceClicked, attr: { 'data-choice-uuid': UUID, 'data-range-value': rangeValue, title: textOption }, text: textOption, css: (i && i == 2) ? 'selected' : ''"></a>
</div>
</div>
<div class="mobile-bar">
<input type="range" data-anchor="range" class="custom-range" min="0" max="100" value="50" autocomplete="off" data-bind="event: { change: $parent.adjustRangeSlider }">
</div>
</div>
</div>
<div class="choice-buttons">
<div class="choice-button">
<a data-bind="click: $parent.showCareers" class="blue-link" title="Show Careers">Show Careers</a>
</div>
<!-- ko if: index == ($parent.quizQuestions().length - 1) -->
<div class="choice-button">
<a class="pink-button results-button" data-bind="click: $parent.getResults" title="Get Results">Get Results <i class="fa fa-cog fa-spin d-none"></i></a>
</div>
<!-- /ko -->
<!-- ko ifnot: index == ($parent.quizQuestions().length - 1) -->
<div class="choice-button">
<a class="pink-button next-question" data-bind="click: $parent.showNext" title="Next Question">Next Question</a>
</div>
<!-- /ko -->
</div>
<div class="quiz-progress">
<p class="progress-text" data-bind="text: $parent.progressText"></p>
</div>
</div>
</form>
<!-- /ko -->
<!-- ko ifnot: quizQuestions -->
<div class="error-message">
<p>
Sorry, something went wrong. I guess you'll have to figure out what to do on your own..
</p>
</div>
<!-- /ko -->
I have had issues with javascript object literals before and would probably approach it like this. Not sure if this works as I haven't tested it.
define([
'jquery',
'uiComponent',
'ko'
], function($, Component, ko) {
'use strict';
var self = this;
self.progressText = ko.observable(false);
function initialize() {
this._super();
self.progressText('1 of 15 questions completed');
return this;
}
function showNext() {
let dataIndex = parseInt($('.quiz-card.show').attr('data-index')) + 1;
alert(ko.isObservable(self.progressText));
alert(self.progressText());
self.progressText(dataIndex + ' of 15 questions completed');
}
return Component.extend({
defaults: {
template: 'Shmoop_Cms/career-form'
},
progressText: self.progressText,
initialize: initialize,
showNext: showNext
});
});
I have a grid(html-table) with a lot of columns and want to combine filtering and sorting on this table.
At the moment I only use filtering on one column but sorting on several columns.
I want to do this with ajax.
I read an article [http://www.craigburke.com/2011/01/23/grails-ajax-list-with-paging-sorting-and-filtering.html]
and tried to adapt it to my version of grails-3.2.6.
This has been very hard to solve and now I'm totally stuck.
If I add something in the filter nothing happens but when I click on a column, the filtering takes place and also sorting, clicking a second time, ajax is not called and the filter is overwritten with the default value. I have managed to implement it on a test project which act the same.
It is much code and maybe there is a way to include the whole project in this question in some way?
I'll try to show most of the important part here if it could help.
The index.gsp:
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main" />
<g:set var="entityName" value="${message(code: 'person.label', default: 'Person')}" />
<title><g:message code="default.list.label" args="[entityName]" /></title>
<script type="text/javascript">
$(document).ready(function() {
setupGridAjax();
setupFilterAjax();
});
</script>
<script type="text/javascript">
function setupGridAjax() {
$('#gridPersons').find('.paginateButtons a, th.sortable a').on('click', function(event) {
event.preventDefault();
var url = $(this).attr('href');
var grid = $(this).parents("table.ajax");
$(grid).html($("#spinner").html());
$.ajax({
type: 'GET',
url: url,
data: [tag],
success: function(data) {
$(grid).fadeOut('fast', function() {$(this).html(data).fadeIn('slow');});
}
})
});
}
</script>
<script type="text/javascript">
// Turn any input changes or form submission within a filter div into an ajax call
function setupFilterAjax(){
alert('FILTER--Anropat');
$('div.filters select:input').on('change',function(event) {
var filterBox = $(this).parents("div.filters");
filterGrid(filterBox);
});
$("div.filters form").submit(function() {
var filterBox = $(this).parents("div.filters");
alert('FILTERBOX - '+filterBox);
filterGrid(filterBox);
return false;
});
}
// Reload grid based on selections from the filter
function filterGrid(filterBox) {
alert('FILTER-change detected');
var grid = $(filterBox).next("div.gridPersons");
$(grid).html($("#spinner").html());
var form = $(filterBox).find("form");
var url = $(form).attr("action");
var data = $(form).serialize();
alert('FILTERGRID - '+url);
$.ajax({
type: 'POST',
url: '${g.createLink( controller:'person', action:'index' )}',
data: [tag],
success: function(data) {
$(grid).fadeOut('fast', function() {$(this).html(data).fadeIn('slow');});
}
});
}
</script>
</head>
<body>
<g:message code="default.link.skip.label" default="Skip to content…"/>
<div class="nav" role="navigation">
<ul>
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
</ul>
</div>
<div id="list-person" class="content scaffold-list" role="main">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<div class="filters">
<g:form action="register">
<div id="selectMill">
Select tags:
<g:select class="selected" name="tag" from="${tagList}" value="${filters?.tag}" noSelection = "${['':'All']}" optionValue="" optionKey="" />
</div>
<div id="gridPersons">
<g:render template="Grid_Persons" model="personList" />
</div>
<div class="pagination">
<g:paginate total="${personCount ?: 0}" />
</div>
<fieldset class="buttons">
<input class="save" type="submit" value="${message(code: 'offer.create.from.buffer.label', default: 'Create')}" />
</fieldset>
</g:form>
</div>
</div>
</body>
The template: _Grid_Persons.gsp
<table class="ajax">
<thead>
<tr>
<g:sortableColumn property='reg' title='Register' />
<g:sortableColumn property="id" title='Id' params="${filters}"/>
<g:sortableColumn property='name' title='Name' params="${filters}"/>
<g:sortableColumn property='tag' title='Tag' params="${filters}"/>
<g:sortableColumn property='registered' title='Registered' params="${filters}"/>
</thead>
<tbody>
<g:each in="${personList}" status="i" var="ps">
<tr class="${ (i % 2) == 0 ? 'even': 'odd'}">
<td><g:checkBox name="ckb" value="${ps.id}" checked="false" /></td>
<td><g:link action="edit" id="${ps.id}">${ps.id}</g:link></td>
<td>${ps.name}</td>
<td>${ps.tag}</td>
<td>${ps.registered}</td>
<td>
</g:each>
</tbody>
The index-part of the controller:
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
def tagList = Person.withCriteria {
projections {
distinct("tag")
}
}
def List<Person> personList = getPersonList()
// Paging def prodBuffer = getPaginatedList(prodBuffer, max, params.offset?.toInteger())
def filters = [tag: params.tag, sort: params.sort, order: params.order]
def model = [personList: personList, filters:filters, tagList:tagList]
if (request.xhr) {
println("AJAX-Request!!!")
render(template:"Grid_Persons", model:model)
prodBuffer, offerDetails:offerDetails, filters:filters])
} else {
offerDetails: offerDetails, millList: millList, selectedMill:false, prodBufferCount: ProdBuffer.count()]
[personList:personList,tagList:tagList]
}
Person.count(), tagList:tagList]
}
def List<Person> getPersonList() {
println("getPersonList tag: "+params.tag)
def tag = params.tag
def c = Person.createCriteria()
def tempList = c.list {
if (tag) eq("tag", tag)
if (params.sort){
order(params.sort, params.order)
}
}
return tempList
}
From debugging:
The page is loaded:
getPersonList tag: null
Selected "Grails" in the filter:
getPersonList tag: Grails
AJAX-Request!!!
Klicked on the "Name"-column header
getPersonList tag: Grails
AJAX-Request!!!
-- Now the list is sorted(ascending) and filtered
Klicked on the column again:
getPersonList tag: Grails
-- Now the list is resorted(descending) and the filtering still ok but the SELECT TAGS now view "All"
Klicked the column for the third time:
getPersonList tag:
AJAX-Request!!!
-- Now the list show all lines and resorted(ascending)
Now solved by using the recommended plugin - datatables.
In case you are in a hurry you can inject https://datatables.net/ that adds the utilities you require among others
I've got problem with outputting my JSON from server (Node.js) to NGrepeat.
I have tried a lot and debugged with both Firebug and Firefox Web Inspector.
For some reason it will not show the data from the JSON, even then the JSON looks correct when I output it in the Firebug console (using Firefox 39.0).
JSON:
[{ nr:"1", svenska:"test2", spanska:"testo2"},{ nr:"2", svenska:"test3", spanska:"testo3"},{ nr:"3", svenska:"test4", spanska:"testo4"},{ nr:"4", svenska:"test5", spanska:"testo5"},{ nr:"5", svenska:"test6", spanska:"testo6"}]
Angular.js
/**
*
* The client Angular.JS main file for the project
*/
var glosorApp = angular.module('glosorApp',['directives']); /* */
angular.module('directives', [])
.directive('toggleClass', function () {
var directiveDefinitionObject = {
restrict: 'A',
template: '<div ng-click="localFunction()" ng-class="selected" ng-transclude></div>',
replace: false,
scope: {
model: '='
},
transclude: true,
link: function (scope, element, attrs) {
scope.localFunction = function () {
scope.model.value = scope.$id;
};
scope.$watch('model.value', function () {
if (scope.model.value === scope.$id) {
scope.selected = "active";
} else {
scope.selected = '';
}
});
}
};
return directiveDefinitionObject;
});
glosorApp.controller('listController', function ($scope, $http) {
$http.post(PROJEKT_SOKVAG+'/_myroute',
{type:AJAX.LISTA_GLOSOR_CLI_R}).success( function(data) {
console.log("Kommer hit lookdeep: "+lookdeep(data.data));
$scope.glosor = data.data;
// $scope.$apply();
console.log("Kommer hit lookdeep: $scope.glosor "+lookdeep($scope.glosor));
});
});
function ajaxanrop(callback, data_) {
$.ajax({
url: PROJEKT_SOKVAG+'_myroute',
type: 'POST',
dataType: 'json',
data: data_ , // the data that should be sent to server
success: function(data) { if ( callback ) callback(data); },
error: function() { if ( callback ) callback(null); },
complete: function() { /* console.log("Klart"); */ }
});
}
HTML (EJS template)
<!DOCTYPE html>
<html ng-app="glosorApp" ng-init="model = { value: 'dsf'}">
<head>
<title>Glosor</title>
<script type="text/javascript">
var PROJEKT_SOKVAG="<%=project_path %>", LoginUser="<%= user.username %>";
</script>
<script type="text/javascript" src="http://w42.se/webbroot/js/jquery-1.9.1.min.js"></script>
<script src="http://w42.se/<%=project_path %>/jquery-ui-1.js" type="text/javascript"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.js"></script>
<script type="text/javascript" src="http://w42.se<%=project_path %>/lookdeep.js"></script>
<script type="text/javascript" src="http://w42.se<%=project_path %>/constants.js"></script>
<script type="text/javascript" src="http://w42.se<%=project_path %>/clientAngular.js"></script>
</head>
<body>
<div id="vald_sida" style="display:none;" class="mentorer"></div>
<p>Användarnamn: <%= user.username %></p>
<p>Email: <%= user.email %></p>
<p>Log out</p>
<div id="wrapper" ng-controller="listController" >
<p>Lägg till Glosor</p>
<p>Testa Glosor</p>
<div class="clear"></div>
<div class="list">
<div class="filterarea">
<h4>Sökning</h4>
<div>
<span>Nr: <input ng-model="search.nr" ng-model-options="{debounce: 20000}"></span>
<span>Svenska: <input ng-model="search.svenska" ng-model-options="{debounce: 20000}"></span>
<span>Spanske: <input ng-model="search.spanska" ng-model-options="{debounce: 20000}"></span>
<span>Poäng: <input ng-model="search.poang" ng-model-options="{debounce: 20000}"></span>
</div>
</div>
<div class="pad clearfix">
<h4>Resultat</h4>
Välj: alla <input type="checkbox" ng-model="master"><br/>
<div ng-form="">
<div class="list">
<div class="thead">
<div>
<span class="sortable"><a href=""
ng-click="predicate = 'nr'; reverse=!reverse">#</a></span>
<span class="sortable td"><a href=""
ng-click="predicate = 'svenska'; reverse=!reverse">Svenska</a></span>
<span class="sortable td"><a href=""
ng-click="predicate = 'spanska'; reverse=!reverse">Spanska</a></span>
<span class="sortable td"><a href=""
ng-click="predicate = 'poang'; reverse=!reverse">Poäng</a></span>
</div>
</div>
<div class="tbody">
<div ng-repeat="glosa in filter_glosor = (glosor | orderBy:predicate:reverse | filter:search)">
<div class="tr" toggle-class="" model="model">
<span class="td">{{glosor.nr}}</span>
<span class="td">{{glosor.svenska}}</span>
<span class="td">{{glosor.spanska}}</span>
</body>
</html>
As for the ng-repeat issue, it looks like you're using a filter incorrectly. Try this:
<div ng-repeat="glosa in glosor | orderBy:predicate:reverse | filter:search">
NB If you're trying to GET data, you should use $http.get instead of the $http.post(PROJEKT_SOKVAG+'/_myroute', you have.
It will save confusion down the road when maintaining the app.
Well, I found the error that I made, and it was a ridiculously simple error. Quite embarrasing actually... ;-)
I put glosor.svenska instead of glosa.svenska in the repeated block (inside the NGRepeat).
That's the bad thing with having the array name and the element name too similar, you might mix them up.
This means that I put the array instead of the repeated single array element inside the repeated block.
I have a dojox/mvc/Repeat area which is bound to an array of records.
Within the row of the Repeat there is a field (the id of the record) which should be a simple display Output if the record has already been saved to the database, but it should be a TextBox if the record is new (the user must enter the value).
How do I solve this elegantly? I am fairly new to Dojo and its MVC part is very under-documented.
The most MVC-ish solution I have found so far is as follows:
1)
I put a "hasBeenSaved" property into the model which will mark the server-side saved state of the record. This attribute will be bound to the view with a transformation since the "display" style attribute of the DIV will be bound to the hasBeenSaved model attribute (one is a boolean the other is a string: "block"/"none").
2)
Within the Row, I put a conditionally visible div around the id input field. This will be visible only when the record is new, so its display style attribute is bound with an appropriate transformer attached to the Dojo MVC binding.
The same is done for the id output field but the transformer is different on the binding since this will be displayed only when the record has already been saved.
The JSFiddle which I have used to prototype this solution: http://jsfiddle.net/asoltesz/6t4dj1w7/15/
require([
"dojo/_base/declare", "dojo/dom-style", "dojo/parser", "dojo/ready",
"dijit/_WidgetBase", "dijit/_TemplatedMixin",
'dojox/mvc/getStateful'
], function(
declare, domStyle, parser, ready,
_WidgetBase, _TemplatedMixin,
getStateful
){
// setting up the data model for MVC
model = {
items: [
{ id: 'id1',
hasBeenSaved: true
},
{ id: null,
hasBeenSaved: false
},
{ id: null,
hasBeenSaved: false
},
{ id: 'id3',
hasBeenSaved: true
}
]
};
model = getStateful(model);
/**
* This mixin makes it possible to set the "display" style property of
* the DOM node (of any widget) as a Widget property and thus bind it to an MVC model
* when needed.
*/
declare("_DisplayAttributeMixin", [], {
// parameters
display: "block",
_setDisplayAttr: function(/*String*/ display){
this._set("display", display);
domStyle.set(this.domNode, "display", display);
}
});
/** Transformer methods for converting hasBeenSaved to visible/hidden values */
transfSavedToHidden = {format: function(hasBeenSaved){
console.log("transfSavedToHidden: " + (hasBeenSaved ? "none" : "block"));
return hasBeenSaved ? "none" : "block";
}};
transfSavedToVisible = {format: function(hasBeenSaved){
console.log("transfSavedToHidden: " + (hasBeenSaved ? "block" : "none"));
return hasBeenSaved ? "block" : "none";
}};
ready(function(){
// Call the parser manually so it runs after our mixin is defined, and page has finished loading
parser.parse();
});
});
The HTML markup:
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: model"
>
<div id="repeatId"
data-dojo-type="dojox/mvc/Repeat"
data-dojo-props="children: at('rel:', 'items')"
>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: at('rel:', ${this.index})"
>
<span>Record: ${this.index}</span>
<!-- This is displayed only when the record is new (not saved yet) -->
<div
data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="_DisplayAttributeMixin"
data-mvc-bindings="
display: at('rel:', 'hasBeenSaved')
.direction(at.from)
.transform(transfSavedToHidden)"
>
<label for="idInput${this.index}">id:</label>
<input
data-dojo-type="dijit/form/TextBox"
id="idInput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></input>
</div> <!-- end conditionally hidden div -->
<!-- This is displayed only when the record has already been saved -->
<div
data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="_DisplayAttributeMixin"
data-mvc-bindings="
display: at('rel:', 'hasBeenSaved')
.direction(at.from)
.transform(transfSavedToVisible)"
>
<label for="idInput${this.index}">id:</label>
<span
data-dojo-type="dojox/mvc/Output"
id="idOutput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></span>
</div> <!-- end conditionally hidden div -->
<hr/>
</div> <!-- end of row -->
</div> <!-- end of Repeat -->
</div> <!-- end of Group -->
A secondary, less complex solution:
Bind the "hasBeenSaved" property to a hidden text within the repeating div.
Put an onChange event on the hidden field which gets the index of the repeat as well.
The onChange event simply hides the field which is not appropriate in light of the hasBeenChanged property value for the record.
The fiddle is here: http://jsfiddle.net/asoltesz/8u9js6sz/5/
Code:
hasBeenSavedChanged = function(field, index) {
var divToHide
if (field.value == true) {
divToHide = "idInputDiv"
}
else {
divToHide = "idOutputDiv"
}
var div = document.getElementById(divToHide + index);
div.style.display = "none";
}
require([
"dojo/_base/declare", "dojo/dom-style", "dojo/parser", "dojo/ready",
"dijit/_WidgetBase", "dijit/_TemplatedMixin",
'dojox/mvc/getStateful'
], function(
declare, domStyle, parser, ready,
_WidgetBase, _TemplatedMixin,
getStateful
){
// setting up the data model for MVC
model = {
items: [
{ id: 'id1',
hasBeenSaved: true
},
{ id: null,
hasBeenSaved: false
},
{ id: null,
hasBeenSaved: false
},
{ id: 'id3',
hasBeenSaved: true
}
]
};
model = getStateful(model);
ready(function(){
// Call the parser manually so it runs after our mixin is defined, and page has finished loading
parser.parse();
});
});
HTML:
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: model"
>
<span id="itemsCtl"
data-dojo-type="dojox/mvc/ListController"
data-dojo-props="model: model.items">
</span>
<div id="itemsRepeat"
data-dojo-type="dojox/mvc/Repeat"
data-dojo-props="children: at('rel:', 'items')"
>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: at('rel:', ${this.index})"
>
<span>Record: ${this.index}</span>
<input
id="hasBeenChanged${this.index}"
data-dojo-type="dijit/form/TextBox"
data-dojo-props="value: at('rel:', 'hasBeenSaved')"
onChange="hasBeenSavedChanged(this, '${this.index}');"
type="hidden"
></input>
<!-- This is displayed only when the record is new (not saved yet) -->
<div id="idInputDiv${this.index}"
>
<label for="idInput${this.index}">id:</label>
<input
data-dojo-type="dijit/form/TextBox"
id="idInput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></input>
</div> <!-- end conditionally hidden div -->
<!-- This is displayed only when the record has already been saved -->
<div id="idOutputDiv${this.index}" >
<label for="idInput${this.index}">id:</label>
<span
data-dojo-type="dojox/mvc/Output"
id="idOutput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></span>
</div> <!-- end conditionally hidden div -->
<hr/>
</div> <!-- end of row -->
</div> <!-- end of Repeat -->
</div> <!-- end of Group -->
Mixin/transformer approach is something I had in mind, too. Two things I’d add there are out-of-the-box Dijit features, one is dijitDisplayNone class, another is attribute mapping feature.
Though it’s strange that the former is undocumented, the intent may have been for private usage within Dijit codebase.
Though it’s a bit hackish (and may be broken in future 1.x Dijit releases), overriding the Dijit code that’s responsible for attribute mapping will allow you to map a widget attribute to a CSS class that’s toggled.
Here’s a code sample that uses the above two:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dojo/resources/dojo.css">
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dijit/themes/dijit.css">
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dojo/dojo.js" data-dojo-config="async: 1, parseOnLoad: 0"></script>
<script type="text/javascript">
require([
"dojo/_base/array",
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/dom-class",
"dojo/parser",
"dojox/mvc/getStateful",
"dijit/form/TextBox"
], function(array, declare, lang, domClass, parser, getStateful){
declare("CssToggleMixin", null, {
// summary:
// Mixin class to support widget attributes with toggleClass type.
// toggleClass type allows boolean value of an attribute to reflect existence of a CSS class in a DOM node in the widget.
_attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){
// summary:
// Handle widget attribute with toggleClass type.
// See dijit/_WidgetBase._attrToDom() for more details.
var callee = arguments.callee;
array.forEach((function(){ return lang.isArray(commands) ? commands.slice(0) : [commands]; })(arguments.length >= 3 ? commands : this.attributeMap[attr]), function(command){
command.type != "toggleClass" ?
this.inherited("_attrToDom", lang.mixin([attr, value, command], {callee: callee})) :
domClass.toggle(this[command.node || "domNode"], command.className || attr, value);
}, this);
}
});
flipConverter = {
format: function (value) {
return !value;
},
parse: function (value) {
return !value;
}
};
model = getStateful({
items: [
{
value: "Foo",
hasBeenSaved: true
},
{
hasBeenSaved: false
},
{
hasBeenSaved: false
},
{
value: "Bar",
hasBeenSaved: true
}
]
});
parser.parse();
})
</script>
</head>
<body>
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div data-dojo-type="dojox/mvc/WidgetList"
data-dojo-mixins="dojox/mvc/_InlineTemplateMixin"
data-dojo-props="children: at(model, 'items')">
<script type="dojox/mvc/InlineTemplate">
<div>
<span data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="CssToggleMixin"
data-dojo-props="value: at('rel:', 'value'),
noDisplay: at('rel:', 'hasBeenSaved').transform(flipConverter),
_setValueAttr: {node: 'domNode', type: 'innerText'},
_setNoDisplayAttr: {type: 'toggleClass', className: 'dijitDisplayNone'}"></span>
<span data-dojo-type="dijit/form/TextBox"
data-dojo-mixins="CssToggleMixin"
data-dojo-props="value: at('rel:', 'value'),
noDisplay: at('rel:', 'hasBeenSaved'),
_setNoDisplayAttr: {type: 'toggleClass', className: 'dijitDisplayNone'}"></span>
</div>
</script>
</div>
</body>
</html>
Hope it’ll shed some light.
Best, Akira
I have strange problem with an MVC3 application. The application works as expected on Firefox and Chrome but not IE.
I have a partial view that is repeated in a loop on a page. At the bottom of the partial view I am doing a check on Request.IsAuthenticated. If authenticated an ajax form should display. This works on all browsers but IE. In IE if there is more than one instance of the partial view rendered the form does not appear (even when the user is logged in).
I have attached the partial view code below. The form is right at the bottom.
Has anyone else had a similar problem? Any help or thoughts would be great. Thank you
#using HERE_MVC.Extenstions
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
#model HERE_MVC.Models.TilesModel
#{
Layout = String.Empty;
}
#foreach (var t in Model.NearestTiles)
{
<div id="div_tile #t.TileId" style="float: left; height: 235px; width: 300px; border: 0px solid black; margin: 5px;
background-color: #DDDDDD;">
<div id="tile #t.TileId" style="background-image: url(#Url.Action("GetTileBgImageWithCache", "Account", new { userName = #t.aspnet_Users.UserName })); background-repeat:no-repeat; height: 200px; width: 300px; border: 0px solid black;">
#t.aspnet_Users.UserName
<br />
#t.Title
<br />
#t.Quote1
<br />
#t.UpdatedDateTime
</div>
<div id="collectbtn #t.TileId" style="padding-top: 5px;">
#Html.ActionImage("Profile", "Home", new { userName = #t.aspnet_Users.UserName }, "~/content/img/Profile-Icon.jpg", "View Profile", "25")
#if (Request.IsAuthenticated)
{
if (#User.Identity.Name == #t.aspnet_Users.UserName)
{
}
else if (!Model.CheckAlreadyFollowing(#User.Identity.Name, #t.aspnet_Users.UserName))
{
#Ajax.ActionImageLink("../content/img/add.jpg", "Collect Tile", "25", "imgOn1" + #t.aspnet_Users.UserName, "AddFollowedUser", "Home", new { userToFollowName = #t.aspnet_Users.UserName }, new AjaxOptions { HttpMethod = "POST", OnSuccess = " $(#imgOn1" + #t.aspnet_Users.UserName + ").hide();" })
}
else
{
#Ajax.ActionImageLink("../content/img/minus.jpg", "Drop Tile", "25", "imgOff2" + #t.aspnet_Users.UserName, "UnFollow", "Home", new { userToUnFollowName = #t.aspnet_Users.UserName }, new AjaxOptions { HttpMethod = "POST" })
}
}
<a href="http://www.facebook.com/sharer.php?u=#t.ShareLink&t=%22+encodeURIComponent(document.title)" onclick="PopupCenter('http://www.facebook.com/sharer.php?u=#t.ShareLink&t=%22+encodeURIComponent(document.title)', 'NearRoar - Facebook',680,400); return false;" class="pin-it-button" count-layout="none">
<img src="../../Content/img/Facebook-share.jpg" width="25px" alt="Facebook share" /></a>
<a href="http://pinterest.com/pin/create/button/?url=#t.ShareLink&media=#t.PintrestShareImage&description=#t.PinterestShareText" onclick="PopupCenter('http://pinterest.com/pin/create/button/?url=#t.ShareLink&media=#t.PintrestShareImage&description=#t.PinterestShareText', 'NearRoar - Pin It',600,400); return false;" class="pin-it-button" count-layout="none" target="_blank">
<img border="0" src="../../Content/img/pintrest.jpg" title="Pin It" width="25px" /></a>
<a href="https://twitter.com/share" class="twitter-share-button" data-hashtags="#t.TwitterHashTag,NearRoar" data-url="#t.ShareLink" data-count="none" data-lang="en">
</a>
<script type="text/javascript" src="//platform.twitter.com/widgets.js"></script>
</div>
<div class="msg_list">
<p class="msg_head #t.TileId">
Comments (<span id='commentCount '#t.TileId>#t.CommentLines.Count</span>)</p>
<div class="msg_body">
#foreach (var c in t.CommentLines)
{
<div class="clearfix">
<div class="msg_body_comment_item_image">
<img width="50" src="#Url.Action("GetImageWithCache", "Home", new { userName = #c.aspnet_Users.UserName })"/>
</div>
<div class="msg_body_comment_item_text">
#c.CommentLine1
<br />
#c.CommentTime
</div>
</div>
}
<div id="newComment #t.TileId">
</div>
<div id="formdiv #t.TileId">
#if (Request.IsAuthenticated)
{
<div>
#using (Ajax.BeginForm("Comment" + #t.TileId, "Home", new { tileId = #t.TileId }, new AjaxOptions { }, new { id = "form" + #t.TileId, name = "form" + #t.TileId }))
{
<fieldset>
#Html.TextBox("commentText " + #t.TileId, null, new { #class = "msg_body_comment_text_input", maxlength = 200 })
<input type="hidden" name="date" id="cmTimeHidden #t.TileId" value="" />
<input type="hidden" name="commentText" id="commentTextHidden #t.TileId" value="" />
<input type="submit" value="Post Comment" onClick="setHiddens(#t.TileId);setNewComment(#t.TileId);"/>
</fieldset>
}
</div>
}
</div>
</div>
</div>
</div>
}
I would double check that rendered markup is valid. It's possible that one of your IDs is duplicated and that IE is just kicking it away.
UPDATE: You have 4 elements with ID t.titleId. As I said above, you can have only one specific ID on the HTML page. Move this to class instead and all should be fine.