How to check if Form data have been modified? - asp.net-mvc-4

I have a pretty complicated form and I don't want to make all the database queries, just because someone entered the update page.
How I would know if the content inside the form has been modified?
I don't use knockout.js and don't want to implement it.

You could use javascript. For example once the form is rendered you could calculate a checksum and before submitting it recalculate this checksum and compare the 2 values. Then you could set the value of a hidden field so that when the form is submitted to the server you would know whether something was modified:
String.prototype.hashCode = function() {
var hash = 0;
if (this.length == 0) return hash;
for (i = 0; i < this.length; i++) {
char = this.charCodeAt(i);
hash = ((hash<<5) - hash) + char;
hash = hash & hash;
}
return hash;
};
$(function() {
var $form = $('form');
$form.data('checksum', $form.serialize().hashCode())
$form.submit(function() {
var initialChecksum = $(this).data('checksum');
var currentChecksum = $(this).serialize();
var isDirty = initialChecksum != currentChecksum;
$('#isDirty').val(isDirty);
});
});
and your view model could have an IsDirty boolean property that you could query in the controller action that is processing the form submission.

maybe with some jQuery. assign some classes to field and fire the change event and put the name of the changed field in some hidden field, then you can have the field which have changed on server side.
<input type="hidden" id="changedNames" name ="changedNames" />
$(function () {
$('.observe').change(function (e) {
var thisName = $(this).attr('name');
$('#changedNames').val($('#changedNames').val() + thisName + " ");
});
});

Related

How to add new fileds/values to line widgets in Barcode mobile view in Odoo14 EE?

Hi i am trying to add two new fields to the Barcode mobile view. I went through the default js code of odoo, but didn't find a way to add my custome fields to it.
Here is the default code
stock_barcode/static/src/js/client_action/lines_widget.js
init: function (parent, page, pageIndex, nbPages) {
this._super.apply(this, arguments);
this.page = page; #don't know where this argument page coming from in argument list.
this.pageIndex = pageIndex;
this.nbPages = nbPages;
this.mode = parent.mode;
this.groups = parent.groups;
this.model = parent.actionParams.model;
this.show_entire_packs = parent.show_entire_packs;
this.displayControlButtons = this.nbPages > 0 && parent._isControlButtonsEnabled();
this.displayOptionalButtons = parent._isOptionalButtonsEnabled();
this.isPickingRelated = parent._isPickingRelated();
this.isImmediatePicking = parent.isImmediatePicking ? true : false;
this.sourceLocations = parent.sourceLocations;
this.destinationLocations = parent.destinationLocations;
// detect if touchscreen (more complicated than one would expect due to browser differences...)
this.istouchSupported = 'ontouchend' in document ||
'ontouchstart' in document ||
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0;
},
In _renderLines function,
_renderLines: function () {
//Skipped some codes here
// Render and append the lines, if any.
var $body = this.$el.filter('.o_barcode_lines');
console.log('this model',this.model);
if (this.page.lines.length) {
var $lines = $(QWeb.render('stock_barcode_lines_template', {
lines: this.getProductLines(this.page.lines),
packageLines: this.getPackageLines(this.page.lines),
model: this.model,
groups: this.groups,
isPickingRelated: this.isPickingRelated,
istouchSupported: this.istouchSupported,
}));
$body.prepend($lines);
for (const line of $lines) {
if (line.dataset) {
this._updateIncrementButtons($(line));
}
}
$lines.on('click', '.o_edit', this._onClickEditLine.bind(this));
$lines.on('click', '.o_package_content', this._onClickTruckLine.bind(this));
}
In the above code, you can see this.page.lines field, i need to add my custom two more fields.
Actually it's dead-end for me.
Any solution?

How to send the index of a for loop into the promise of a function in a Vue Resource call?

I am looping through an object however in the asynchronous part the i variable is always five.
How can I maintain that value, or pass it into the function
getProductData: function() {
var vm = this;
for (var i = 0; i < vm.recommendationResponse['recommendedItems'].length; i++) {
var sku = vm.recommendationResponse['recommendedItems'][i]['items'][0]['id'];
vm.$http.get('http://127.0.0.1:8000/models/api/productimage/' + sku).then(response => {
// get body data
vm.recommendationResponse['recommendedItems'][i]['items'][0]['image_url'] = response.body['product_image_url'];
vm.recommendationResponse['recommendedItems'][i]['items'][0]['price'] = response.body['price'];
}, response => {
vm.recommendationResponse['recommendedItems'][i]['items'][0]['image_url'] = '';
vm.recommendationResponse['recommendedItems'][i]['items'][0]['price'] = '';
});
}
}
I I do something like this:
vm.$http.get('http://127.0.0.1:8000/models/api/productimage/' + sku).then((response, i) => ...
then i is undefined
Who do I keep the index of the loop or should I go about it a different way?
Always use let to initialize variables in for loop when dealing with async operations. Similar things goes to having for loops in intervals. By using let you make sure you always have a unique variable assigned to i.
for (let i = 0, recommendationlength = vm.recommendationResponse['recommendedItems'].length; i < recommendationlength; i++)
Little bonus, if you cache array length in the beginning you get a small performance boost :-)
You could use Array.prototype.forEach instead:
var vm = this;
vm.recommendataionResponse['recommendedItems'].forEach((item, i) => {
var sku = vm.recommendationResponse['recommendedItems'][i]['items'][0]['id'];
vm.$http.get('http://127.0.0.1:8000/models/api/productimage/' + sku).then(response => {
// get body data
vm.recommendationResponse['recommendedItems'][i]['items'][0]['image_url'] = response.body['product_image_url'];
vm.recommendationResponse['recommendedItems'][i]['items'][0]['price'] = response.body['price'];
}, response => {
vm.recommendationResponse['recommendedItems'][i]['items'][0]['image_url'] = '';
vm.recommendationResponse['recommendedItems'][i]['items'][0]['price'] = '';
});
})
This way, since there is a unique scope for each i value, each .then callback will be able to reference the correct value.

Triggering clearIconTap callback on dynamically created fields

I am creating a simple app using Sencha Touch where I'm dynamically creating container with textfields, textareafields etc. when the user needs to add new container with components. The problem now is when the clear icon on the textareafield is tapped it clears the text, but I would like to know which textareafield has been cleared. Can anyone help me in this please?
This is how I created container .
var childObj2 = {};
childObj2.xtype = 'container';
var type = 'vbox';
var layout = {}
layout.type = type;
childObj1.layout = layout;
var txtarea= {};
txtarea.xtype = 'textareafield';
txtarea.id = "txt51";
txtarea.flex = 3;
txtarea.maxRows = 7;
txtarea.placeHolder = 'Type here';
txtarea.value = value['notes'];
txtarea.inputCls = 'txtareaStyle'
txtarea.clearicontap = "clearText";
How to add clearicontap listener to this?
When you you create a textfield simply add a listener to it for the clearicontap event and that callback will get executed for each of the fields.
For example:
var container = Ext.create('Ext.Container', {});
for (var i=1; i<=3; i++) {
var field = Ext.create('Ext.field.Text', {
id: 'textfieldnumber' + i,
listeners: {
clearicontap: function() {
alert("Tapped clear icon on text field number: " + i + "!");
}
}
});
container.add(field);
}
[EDIT]
I answer the question you make after your edit:
I am using the standard way of creating Sencha components through Ext.create(), and I would suggest you to switch to the same way. It is not clear by the code you posted how those Javascript objects are actually transformed into Ext components. Anyway, they are very likely components configurations, so I guess you could try:
txtarea.listeners = {
clearicontap: function() {
alert("Tapped clear icon on text field");
}
}

Cannot dynamically change a caption 'track' in Video.js

I'm coding a basic video marquee and one of the key requirements is that the videos need to be able to advance while keeping the player in full screen.
Using Video.js (4.1.0) I have been able to get everything work correctly except that I cannot get the captions to change when switching to another video.
Either inserting a "track" tag when the player HTML is first created or adding a track to the 'options' object when the player is initialized are the only ways I can get the player to display the "CC" button and show captions. However, I cannot re-initialize the player while in full screen so changing the track that way will not work.
I have tried addTextTrack and addTextTracks and both show that the tracks have been added - using something like console.log(videoObject.textTracks()) - but the player never shows them or the "CC" button.
Here is my code, any help is greatly appreciated:
;(function(window,undefined) {
// VIDEOS OBJECT
var videos = [
{"volume":"70","title":"TEST 1","url":"test1.mp4","type":"mp4"},
{"volume":"80","title":"TEST 2","url":"test2.mp4","type":"mp4"},
{"volume":"90","title":"TEST 3","url":"test3.mp4","type":"mp4"}
];
// CONSTANTS
var VIDEO_BOX_ID = "jbunow_marquee_video_box", NAV_TEXT_ID = "jbunow_marquee_nav_text", NAV_ARROWS_ID = "jbunow_marquee_nav_arrows", VIDEO_OBJ_ID = "jbunow_marquee_video", NAV_PREV_ID = "jbunow_nav_prev", NAV_NEXT_ID = "jbunow_nav_next";
// GLOBAL VARIABLS
var videoObject;
var currentTrack = 0;
var videoObjectCreated = false;
var controlBarHideTimeout;
jQuery(document).ready(function(){
// CREATE NAV ARROWS AND LISTENERS, THEN START MARQUEE
var navArrowsHtml = "<div id='" + NAV_PREV_ID + "' title='Play Previous Video'></div>";
navArrowsHtml += "<div id='" + NAV_NEXT_ID + "' title='Play Next Video'></div>";
jQuery('#' + NAV_ARROWS_ID).html(navArrowsHtml);
jQuery('#' + NAV_PREV_ID).on('click',function() { ChangeVideo(GetPrevVideo()); });
jQuery('#' + NAV_NEXT_ID).on('click',function() { ChangeVideo(GetNextVideo()); });
ChangeVideo(currentTrack);
});
var ChangeVideo = function(newIndex) {
var videoBox = jQuery('#' + VIDEO_BOX_ID);
if (!videoObjectCreated) {
// LOAD PLAYER HTML
videoBox.html(GetPlayerHtml());
// INITIALIZE VIDEO-JS
videojs(VIDEO_OBJ_ID, {}, function(){
videoObject = this;
// LISTENERS
videoObject.on("ended", function() { ChangeVideo(GetNextVideo()); });
videoObject.on("loadeddata", function () { videoObject.play(); });
videoObjectCreated = true;
PlayVideo(newIndex);
});
} else { PlayVideo(newIndex); }
}
var PlayVideo = function(newIndex) {
// TRY ADDING MULTIPLE TRACKS
videoObject.addTextTracks([{ kind: 'captions', label: 'English2', language: 'en', srclang: 'en', src: 'track2.vtt' }]);
// TRY ADDING HTML
//jQuery('#' + VIDEO_OBJ_ID + ' video').eq(0).append("<track kind='captions' src='track2.vtt' srclang='en' label='English' default />");
// TRY ADDING SINGLE TRACK THEN SHOWING USING RETURNED ID
//var newTrack = videoObject.addTextTrack('captions', 'English2', 'en', { kind: 'captions', label: 'English2', language: 'en', srclang: 'en', src: 'track2.vtt' });
//videoObject.showTextTrack(newTrack.id_, newTrack.kind_);
videoObject.volume(parseFloat(videos[newIndex]["volume"]) / 100); // SET START VOLUME
videoObject.src({ type: "video/" + videos[newIndex]["type"], src: videos[newIndex]["url"] }); // SET NEW SRC
videoObject.load();
videoObject.ready(function () {
videoObject.play();
clearTimeout(controlBarHideTimeout);
controlBarHideTimeout = setTimeout(function() { videoObject.controlBar.fadeOut(); }, 2000);
jQuery('#' + NAV_TEXT_ID).fadeOut(150, function() {
currentTrack = newIndex;
var navHtml = "";
navHtml += "<h1>Now Playing</h1><h2>" + videos[newIndex]["title"] + "</h2>";
if (videos.length > 1) { navHtml += "<h1>Up Next</h1><h2>" + videos[GetNextVideo()]["title"] + "</h2>"; }
jQuery('#' + NAV_TEXT_ID).html(navHtml).fadeIn(250);
});
});
}
var GetPlayerHtml = function() {
var playerHtml = "";
playerHtml += "<video id='" + VIDEO_OBJ_ID + "' class='video-js vjs-default-skin' controls='controls' preload='auto' width='560' height='315'>";
playerHtml += "<source src='' type='video/mp4' />";
//playerHtml += "<track kind='captions' src='track.vtt' srclang='en' label='English' default='default' />";
playerHtml += "</video>";
return playerHtml;
}
var GetNextVideo = function() {
if (currentTrack >= videos.length - 1) { return 0; }
else { return (currentTrack + 1); }
}
var GetPrevVideo = function() {
if (currentTrack <= 0) { return videos.length - 1; }
else { return (currentTrack - 1); }
}
})(window);
The current VideoJS implementation (4.4.2) loads every kind of text tracks (subtitles, captions, chapters) on initialization time of the player itself, so it grabs correctly only those, which are defined between the <video> tags.
EDIT: I meant it does load them when calling addTextTrack, but the player UI will never update after initialization time, and will always show the initialization time text tracks.
One possible workaround is if you destroy the complete videojs player and re-create it on video source change after you have refreshed the content between the <video> tags. So this way you don't update the source via the videojs player, but via dynamically adding the required DOM elements and initializing a new player on them. Probably this solution will cause some UI flashes, and is quite non-optimal for the problem. Here is a link about destroying the videojs player
Second option is to add the dynamic text track handling to the existing code, which is not as hard as it sounds if one knows where to look (I did it for only chapters, but could be similar for other text tracks as well). The code below works with the latest official build 4.4.2. Note that I'm using jQuery for removing the text track elements, so if anyone applies these changes as is, jQuery needs to be loaded before videojs.
Edit the video.dev.js file as follows:
1: Add a clearTextTracks function to the Player
vjs.Player.prototype.clearTextTracks = function() {
var tracks = this.textTracks_ = this.textTracks_ || [];
for (var i = 0; i != tracks.length; ++i)
$(tracks[i].el()).remove();
tracks.splice(0, tracks.length);
this.trigger("textTracksChanged");
};
2: Add the new 'textTracksChanged' event trigger to the end of the existing addTextTrack method
vjs.Player.prototype.addTextTrack = function(kind, label, language, options) {
...
this.trigger("textTracksChanged");
}
3: Handle the new event in the TextTrackButton constructor function
vjs.TextTrackButton = vjs.MenuButton.extend({
/** #constructor */
init: function(player, options) {
vjs.MenuButton.call(this, player, options);
if (this.items.length <= 1) {
this.hide();
}
player.on('textTracksChanged', vjs.bind(this, this.refresh));
}
});
4: Implement the refresh method on the TextTrackButton
// removes and recreates the texttrack menu
vjs.TextTrackButton.prototype.refresh = function () {
this.removeChild(this.menu);
this.menu = this.createMenu();
this.addChild(this.menu);
if (this.items && this.items.length <= this.kind_ == "chapters" ? 0 : 1) {
this.hide();
} else
this.show();
};
Sorry, but for now I cannot link to a real working example, I hope the snippets above will be enough as a starting point to anyone intrested in this.
You can use this code when you update the source to a new video. Just call the clearTextTracks method, and add the new text tracks with the addTextTrack method, and the menus now should update themselves.
Doing the exact same thing (or rather NOT doing the exact same thing)... really need to figure out how to dynamically change / add a caption track.
This works to get it playing via the underlying HTML5, but it does not show the videojs CC button:
document.getElementById("HtmlFiveMediaPlayer_html5_api").innerHTML = '<track label="English Captions" srclang="en" kind="captions" src="http://localhost/media/captiontest/demo_Brian/demo_h264_1.vtt" type="text/vtt" default />';

Dojo Datagrid - Get the row number

I am trying to retrieve the line number in dojo data grid. rowIndex function would not help me much because I need the 'line number' and NOT 'row number' when sorted.
The scenario:
I would like to set the focus on one specific row and this focus should remain even after sorting. But if I use the code below, it does not select the correct row.
For example, the index 1 is on the 5th line after sorting. However, the e.item.id still remains as 1, expected is 5.
calendar.on("itemClick", function (e)
{
MyGrid.doclick({ rowIndex: e.item.id });
MyGrid.scrollToRow(e.item.id);
});
Additionally, I also tried...
calendar.on("itemClick", function (e)
{
var identity = MyGrid._by_idx[e.item.id].idty;
var gridItem = MyGrid.getItem(identity);
var gridItemIndex = MyGrid.getItemIndex(gridItem);
MyGrid.doclick({ rowIndex: gridItemIndex });
MyGrid.scrollToRow(e.item.id);
});
Could you please let me know how to get the correct row after fitering? I thank you for your time.
Wishes,
Santosh
Okay, I figured out the answer.
GetGridItemIndexByGridItem = function (gridItem) {
var indexLength = MyGrid._by_idx.length;
var element = null;
var gridItemIndex = -1;
for (var i = 0; i < indexLength; i++) {
element = MyGrid._by_idx[i];
if (element.item.Guid == gridItem.Guid) {
gridItemIndex = i;
}
}
return gridItemIndex;
}
Best Wishes