KendoUI MVVM Table of Contents style menu - templates

Been trying to use KendoUI templates and MVVM to create a menu that resembles a table of contents like so:
Lesson 1:
- Slide 1
- Slide 2
Lesson 2:
- Slide 1
and so on. I have the following data that I've created a kendo.observable with:
var CourseData = kendo.observable({
name: 'HTML Test Course',
lessons: [
{ // Lesson 1
name: 'Lesson 1',
slides: [
{ // Slide 1.1
name: 'Animation',
type: 'CreateJS',
cctext: '<p>...</p>',
}
]
}
]
});
Before discovering KendoUI I built the menu using plain JavaScript and for the given data it turns out like this:
<ul>
<li>
<span>Lesson 1</span>
<ul>
<li onclick="Shell.GoToSlide(1, 1)" class="unlocked"><!-- 0 = locked, 1 = unlocked, 2 = viewed, 3 = completed -->
<span>Animation</span>
</li>
</ul>
</li>
</ul>
The two pieces of this puzzle that I am having trouble expressing together are the progress (noted by the class) and the click event. Progress is stored in either a jagged array right in CourseData (like CourseData.progress = [[3]], so CourseData.progress[lesson][slide] will give the progress of that slide.) or if it would better solve this conundrum I would consider moving it to each slide object (like CourseData.lessons[lesson].slides[slide].progress). The click event calls a navigation function written elsewhere that brings up the slides using their lesson and slide number (base 1) and is not added to the item if progress is locked.
With templates, I can do something like the following:
<script type="text/x-kendo-template" id="coursemap-template">
# for (var l = 0; l < lessons.length; l++) { #
<li>
<span>#: lessons[l].name #</span>
<ul>
# for (var s = 0; s < lessons[l].slides.length; s++) { #
<li onclick="Shell.GoToSlide(#: s + 1 #, #: l + 1 #)"><span>#: lessons[l].slides[s].name #</span></li>
# } #
</ul>
</li>
# } #
</script>
To get easy access to the indices of each lesson and slide, but it won't let me bind to a specific element in an array like: data-bind="attr: { class: progress[l][s] }" when Kendo evaluates this binding later it does not know what l and s are, nor can I think of an elegant way to translate the numbers into the appropriate strings. If I use class="#: ['locked','unlocked','viewed','completed'][progress[l][s]] #" it works but does not automatically update when the progress changed. If I used nested templates and made progress a property of slide I could bind to it no problem but then I wouldn't know how to get the lesson and slide indices for the click event without tons of indexOf calls or parent().parent() shenanigans. Thoughts?

Not the ideal solution, but this question has enough views that I suppose I should provide the code I used as a workaround:
View:
<ul id="coursemap"></ul>
ViewModel:
(function () {
for (var l = 0; l < CourseData.lessons.length; l++) {
var lessonItem = document.createElement('li');
$(lessonItem).attr('data-bind', 'html: lessons[' + l + '].name');
$('#coursemap').append(lessonItem);
var slides = document.createElement('ul');
for (var s = 0; s < CourseData.lessons[l].slides.length; s++) {
var slideItem = document.createElement('li');
$(slideItem).attr('data-bind', 'html: lessons[' + l + '].slides[' + s + '].name, attr: { data-progress: progress[' + l + '][' + s + '] }');
(function (lesson, slide) {
$(slideItem).click(function () {
// If the target slide is not locked, navigate.
if ($(this).attr('data-progress') != 0) {
Shell.GoToSlide(slide, lesson);
ExtensionManager.AutoHide();
}
})
})(l + 1, s + 1);
$(slides).append(slideItem);
}
$('#coursemap').append(slides);
}
kendo.bind($('#coursemap'), CourseData);
})();

Related

Grouping the output of a CouchDB View

I have a map reduce view:
.....
emit( diffYears, doc.xyz );
reduced with _sum.
xyz is then a number which is summed per integer(diffYears).
The output looks roughly like this:
4 1204.9
5 796.19
6 1124.8
7 1112.6
8 1993.62
9 159.26
10 395.41
11 456.05
12 457.97
13 39.80
14 483.68
15 269.469
etc..
What I would like to do is group the results as follows:
Grouping Total per group
0-4 1959.2 i.e add up the xyz's for years 0,1,2,3,4
5-9 3998.5 same for 5,6,7,8,9 ...etc.
10-14 3566.3
I saw a suggestion where a list was used on a view output here: Using a CouchDB view, can I count groups and filter by key range at the same time?
but have been unable to adapt it to get any kind of result.
The code given is:
{
_id: "_design/authors",
views: {
authors_by_date: {
map: function(doc) {
emit(doc.date, doc.author);
}
}
},
lists: {
count_occurrences: function(head, req) {
start({ headers: { "Content-Type": "application/json" }});
var result = {};
var row;
while(row = getRow()) {
var val = row.value;
if(result[val]) result[val]++;
else result[val] = 1;
}
return result;
}
}
}
I substituted var val = row.key in this section:
while(row = getRow()) {
var val = row.value;
if(result[val]) result[val]++;
else result[val] = 1;
}
(although in this case the result is a count.)
This seems to be the way to do it.
(It is like having a startkey and endkey for each grouping which I can do manually, naturally, but not inside a process. Or is there a way of entering multiple start- and endkeys into one GET command???? )
This must be a fairly normal thing to do especially for researchers using statistical analysis.
I assume therefore that it does get done but I cannot locate examples
as far as CouchDB is concerned.
I would appreciate some help with this please or a pointer in the right direction.
Many thanks.
EDIT:
Perhaps the answer lies in a process in 'reduce' to group the output??
You can accomplish what you want using a complex key. The limitation is that the group size is static and needs to be defined in the view.
You'll need a simple step function to create your groups within map like:
var size = 5;
var group = ( doc.diffYears - (doc.diffYears % size)) / size;
emit( [group, doc.diffYears], doc.xyz);
The reduce function can remain _sum.
Now when you query the view use group_level to control the grouping. At group_level=0, everything will be summed and one value will be returned. At group_level=1 you'll receive your desired sums of 0-4, 5-9 etc. At group_level=2 you'll get your original output.

Summing rows on a tabular form in apex 4.2 for validation

How can I sum fields on each row in a tabular form in APEX 4.2 to get a total for that row before I submit the page in order to do page validation?
For example if the first row has 6 in field a and 6 in field b the total for the first row should be 12 and on the second row if field b is 5 and field c is 5 the total for the second row should be 10.
So I want to get totals based on rows not column. Is that possible?
Yes, its possible. If you know javascript/jquery, you'll get along well with my solution. This is what you have to do:
get the name attribute(using inspect element of your browser) of the field you want to sum up. Names of fields in oracle apex usually goes like 'f01' or 'f02' and so on. Once you get the fields, create a javascript function or you can use this one if you like:
function sumUpRows(columnforsum,itemstobeadded){
var numofcols = arguments.length;
var numofrows = $("[name=" + arguments[0] + "]").length;
var summ = 0;
for(x=0;x<numofrows;x++){
for(i=1;i<numofcols;i++){
summ = summ + Number($("[name=" + arguments[i] + "]").eq(x).val());
}
$("[name=" + columnforsum + "]").eq(x).val(summ);
}
}
Put function above in "function and global variable declaration" part of your page. Then create a "before page submit" dynamic action. Set its action to "execute javascript" then put this line of code:
sumUpRows("nameoffieldwheresumwillbeassigned","nameofitem1","nameofitem2","nameofitem3");
Here's a sample :
sumUpRows("f04","f01","f02","f03");
For your question in the comment section, the answer is yes. To get the sum of a row automatically as you fill up the boxes on that row, you can use this function:
function sumUpRowsAsYouGo(whatelement){
var numofrows=$("[name=f01]").length;
var summ = 0;
for(i=0;i<numofrows;i++){
if($(whatelement).attr("id") == $("[name=" + $(whatelement).attr("name") + "]").eq(i).attr("id")){
for(a=1;a<(arguments.length-1);a++){
summ += Number($("[name="+ arguments[a] + "]").eq(i).val());
}
$("[name="+ arguments[arguments.length-1] + "]").eq(i).val(summ);
break;
}
}
}
you can use this function like this(put these lines in the "Execute on Page load" and "after refresh" dynamic action of your tabular form region):
$("[name=f01]").change(function(){
sumUpRowsAsYouGo(this,"f01","f02","f03","f04");
}
);
$("[name=f02]").change(function(){
sumUpRowsAsYouGo(this,"f01","f02","f03","f04");
}
);
$("[name=f03]").change(function(){
sumUpRowsAsYouGo(this,"f01","f02","f03","f04");
}
);

Copy cell if certain condition met

I know there are already many topics like mine, but I am completely new to programming scripts and therefore I ask you to help me with following problem:
In cell B2 is the user name and in cell C2 the submitter type - if the submitter type is "Requester" then the user name of cell B2 should be copied to cell AE2 - the problem is, that I also want to keep the previous user names if the submitter type change and gets updated...
That means, first the submitter type is "Requester" then the system should copy the user name to cell AE2 - if the submitter type of my line change, then the already copied user name should not change - understood what I want? This should work for all lines of the spreadsheet...
I tried the code but got following error:
Maybe you can improve the code for me?
I changed those 2:
Before:
for (var i = 1; i <= numRows; i++) {
var userNameCell = rows.getCell(i, userNameColumn);
var subTypeCell = rows.getCell(i, subTypeColumn);
var sUserNameCell = rows.getCell(i, sUserNameColumn);
After:
for (var i = 1; i <= numRows; i++) {
var userNameCell = rows.getCell(i, userNameColumn);
var sUserNameCell = rows.getCell(i, sUserNameColumn);
var subTypeCell = rows.getCell(i, subTypeColumn);
The error now is:
new error
Or was the change wrong? Maybe you mean that:
var userNameColumn = 2; //Column where the user name is written
var subTypeColumn = 5; //Column where the submitter type is written ex. "Requester"
var sUserNameColumn = 3; //Column where the user name is saved
But then only one object (username) is known instead of 2 before - subTypeCell and sUserNameCell again undefined - that says the debugger - the code looks now like that (again with my changes, because then 2 objects instead only 1 is known):
You can do something like this:
var userNameColumn = 2; //Column where the user name is written
var subTypeColumn = 3; //Column where the submitter type is written ex. "Requester"
var sUserNameColumn = 5; //Column where the user name is saved
function saveUserName() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
for (var i = 1; i <= numRows; i++) {
var userNameCell = rows.getCell(i, userNameColumn);
var subTypeCell = rows.getCell(i, subTypeColumn);
var sUserNameCell = rows.getCell(i, sUserNameColumn);
if(sUserNameCell.isBlank() && subTypeCell.getValue() === 'Requester') {
sUserNameCell.setValue(userNameCell)
};
}
}
Firstly this defines the columns that you are working with, which are the "Username"(in the scriptuserNameColumn), the column with the "Submitter type"(in the scriptsubTypeColumn) and the column where the username is saved (in the scriptsUserNameColumn). Each have been assigned a number corresponding to a columns in spreadsheet, wher 2 is B, 3 is C, and so on. You can change them to your needs.
Then we create a function called saveUserName() inside this function we get the current spreadsheet, we define what range in the spreadsheet we will work with, here it is everything, then we get the number of rows that has data in them.
After that we go through the data we defined, and setup some variables we can use for each cell. Then we create our if statement, which states, if the "Saved-User-Name-Cell" is empty and the "Submitter-Type-Cell" = the word 'Requester' then we grab the "Saved-User-Name-Cell" and set it's value equal to what is within the "User-Name-Cell".
That's it!

Using ng-pattern with ng-model parsing/formatting

I'm trying to be real fancy with angular inputs by using both ng-pattern and ng-model parsing together. The regex I put in ng-pattern works fine on regex101.com, and even logging it in my app it works great. When using it in ng-pattern however, its saying my input is invalid though when it should not be. I'm wondering when ng-pattern does its thing in relation to when ngModel.$parsers/ngModel.$formatters are doing their thing, because i could see that causing it to fail. Here's some code:
Here's the ngModel parsing directive:
UI.directive('formatSeconds', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
var padZero = function (num, size) {
return ('00' + num).substr(-size);
};
var formatSeconds = function (seconds) {
var min = Math.floor(seconds / 60);
var sec = Math.floor(seconds % 60);
var ms = Math.round(((seconds % 60) - sec) * 1000);
return min + ':' + padZero(sec, 2) + ':' + padZero(ms, 3);
};
var parseTime = function (time) {
time = time.split(':');
var min = parseInt(time[0] || 0);
var sec = parseInt(time[1] || 0);
var ms = parseInt(time[2] || 0);
return min * 60 + sec + (ms / 1000);
};
ngModel.$parsers.push(parseTime);
ngModel.$formatters.push(formatSeconds);
}
};
});
Here's my regex in my controller:
$scope.timeRegex = /^([0-9]+)?\:([0-5][0-9])\:?([0-9]{3})?$/;
And here's the relevant part of my view:
<tbody ng-form name="syncForm">
<tr class="timing-entry" ng-repeat="entry in scenario.syncManifest">
<td>
<input type="text" ng-attr-name="{{'time' + $index}}" required ng-model="entry.time" format-seconds ng-pattern="timeRegex" />
</td>
</tr>
</tbody>
The time in entry is in seconds, so the formatter puts it in 0:00:000 format. Then I hoped the ng-pattern would kick in and say yes! valid! But I'm wondering if it is running when the time property is still in 0.000 format before parsing.
Angular validation is performed upon the ng-model value, that is to say:
When a value is inputted into the view, the $parsers will run and the inputted value will be transformed into how you want it stored in the ng-model. In this case, thats a number representing the number of seconds. Then the ng-pattern will work on that value - if it passes validation, ng-model will be set.
When the value is set from the controller, the ng-pattern will go to work on that raw ng-model value. I believe the $formatters will run and the formatted $viewValue will be sent to the view regardless of the validation result.
Either way - in your example, ng-pattern will be working on the integer value of seconds, not the formatted value displayed in the control.

Writing methods that call other methods inside d3 ember components?

Let's say I am creating a reusable component in Ember, and I want a helper function that calls another helper function defined within. For example,
App.SomeCoolComponent = Ember.Component.extend
offset: 50
position: (x) -> x * 100
offsetPosition: # would like (x) -> position(x) + offset
So conceptually, return a function that would evaluate the position at x, add offset, and return value. Obviously this is a silly example and I could just write offSetPosition without calling position, but in a more complex scenario that is repeating code. The problem is I can't figure out how to get this to work. I tried
offsetPosition: (x) -> #get('position')(x) + #get('offset')
which fails because #get isn't defined within the function, it has the wrong scope. I've tried to insert things like Ember.computed in various places also with no luck, e.g. the following also doesn't work:
offsetPosition: Ember.computed(->
(x) -> #get('position')(x) + #get('offset')).property('position', 'offset')
What is the correct way of doing this?
Ember version 1.3.0-beta.1+canary.48513b24. Thanks in advance!
Edit: seems like my problem stems from passing the function into a d3 call. For example:
App.SomeCoolComponent = Ember.Component.extend
offset: 50
position: (d, i) -> i * 100
offsetPosition: (d, i) ->
#position(d, i) + #get('offset')
# Some other code not shown
didInsertElement: ->
data = [1, 2, 3]
i = 1
d = data[i]
console.log(#position(d, i)) # works
console.log(#offsetPosition(d, i)) # works
d3.select('svg').selectAll('circle').data(data).enter().append('circle')
.attr('cx', #position) # works
.attr('cy', #offsetPosition) # fails
.attr('r', 30)
The error message is Uncaught TypeError: Object #<SVGCircleElement> has no method 'position'
Any thoughts on how to resolve this issue?
The problem is that you are passing a function offsetPosition (which references this and expects it to point to App.SomeCoolComponent) to a D3 callback where this is replaced by the DOMElement.
You can solve the problem in two ways:
Using the fat arrow syntax:
d3.select('svg').selectAll('circle').data(data).enter().append('circle')
.attr('cx', #position) # works
.attr('cy', (d, i) => #offsetPosition(d, i))
.attr('r', 30)
Using bind explicitly:
d3.select('svg').selectAll('circle').data(data).enter().append('circle')
.attr('cx', #position) # works
.attr('cy', #offsetPosition.bind(this))
.attr('r', 30)
methods (aka not computed properties) are in the current context and should just be called like a method, and not with getters/setters.
offsetPosition: (x) ->
#position(x) + #get("offset")
position: (x) ->
x * 100
Here's an example: http://emberjs.jsbin.com/eWIYICu/3/edit
App.AnAppleComponent = Ember.Component.extend({
offset: 50,
position: function(x) {
return x * 100;
},
offsetPosition: function(x) {
return this.position(x) + this.get('offset');
},
displayOffset: function(){
return this.offsetPosition(Math.floor(Math.random() * 10) + 1);
}.property('offset')
});
Personally I'd create a mixin and add my methods in there, then add the mixin wherever that logic is needed. Mixins are in the scope of whatever they are added to.
BTW You can use Ember.Get(object, 'propertyOnObject') anywhere in the app.
In response to your edit, you are passing in methods into those attribute values instead of the values of those methods (which is why it works above, but not below). There is a good chance since you are sending in those methods jquery is applying those methods later way out of scope.
didInsertElement: ->
data = [1, 2, 3]
i = 1
d = data[i]
position = #position(d, i)
offsetPosition = #offsetPosition(d, i)
console.log position
console.log offsetPosition
d3.select("svg").selectAll("circle").data(data).enter().append("circle").attr("cx", position).attr("cy", offsetPosition).attr "r", 30
I have a feeling you are wanting this to dynamically update or something along those lines, if that's the case you really want to be using computed properties, the bacon of Ember. Here's an updated version of the apple component:
http://emberjs.jsbin.com/eWIYICu/5/edit
<div {{bind-attr style='dynamicStyle'}}>
dynamicStyle: function(){
var rgb = this.get('rgb'),
rgb1 = rgb * 21 % 255,
rgb2 = rgb * 32 % 255,
rgb3 = rgb * 41 % 255;
return 'background-color:rgb(' + rgb1 + ',' + rgb2 + ',' + rgb3 + ');';
}.property('rgb'),