Back navigation not reloading jwplayer in backbone js and rails 3 app - ruby-on-rails-3

My app is a rails 3 app using backbone.js and jw player for playing a playlist of videos. The index_view for the videos has all the videos loaded into a playlist for jw player. My problem comes when I navigate away from the index page, let's say to the show_view for an individual video. When I click the browser's back button I get an error when the jw player tries to load again.
Uncaught TypeError: Object #<Object> has no method 'setup'
I believe the problem is that the template hasn't loaded the html element that the player uses to instantiate itself. That's my current theory. If you look in index_view.js.coffee you see that in the render method I use the JQuery $(document).read -> method to load the player. If I remove that, the player doesn't load and I see the same error.
Uncaught TypeError: Object #<Object> has no method 'setup'
This error can be reproduced simply by calling the jwplayer on a non-existant CSS id. ie jwplayer('non-existant-id').setup(...) would produce the same error. I'm pretty new to backbone but I would assume that the JQuery document.ready method has no effect after the initial loading of the index page. The DOM is never reloaded once I'm using the # routes, so when I navigate back to the index page, the id 'my-video' doesn't yet exist so calling jwplayer('my-video') produces an error. Is there any sort of Backbone.ready method? :)
So here's some code, lemme know if you need anything else:
index.html.erb The rails view for videos
<div id="videos"></div>
<script type="text/javascript">
$(function() {
// Blog is the app name
window.router = new SeehearmeWebapp.Routers.VideosRouter({videos: <%= #videos.to_json.html_safe -%>, users: <%= #users.to_json.html_safe -%>, questions: <%= #questions.to_json.html_safe -%>});
Backbone.history.start();
});
</script>
videos_router.js.coffee
class SeehearmeWebapp.Routers.VideosRouter extends Backbone.Router
initialize: (options) ->
#videos = new SeehearmeWebapp.Collections.VideosCollection()
#videos.reset options.videos
#users = new SeehearmeWebapp.Collections.UsersCollection()
#users.reset options.users
#playlist = []
#questions = options.questions
for i in [0..#videos.length-1]
video = #videos.models[i]
versions = video.attributes.versions[6]
images = video.attributes.thumbnails[0]
question = #questions[parseInt(video.attributes.question_id)-1]
if !(question == undefined)
title = question.text
else
title = ""
if !(versions == undefined)
creator_id = video.attributes.creator_id.toString()
#playlist.push {file: versions.url, creator_id: creator_id, gender: #users.get(creator_id).attributes.gender, question: title , image: images.url}
routes:
"new" : "newVideo"
"index" : "index"
":id/edit" : "edit"
":id" : "show"
".*" : "index"
newVideo: ->
#view = new SeehearmeWebapp.Views.Videos.NewView(collection: #videos)
$("#videos").html(#view.render().el)
index: ->
#view = new SeehearmeWebapp.Views.Videos.IndexView(videos: #videos, users: #users, playlist: #playlist)
$("#videos").html(#view.render().el)
show: (id) ->
video = #videos.get(id)
#view = new SeehearmeWebapp.Views.Videos.ShowView(model: video)
$("#videos").html(#view.render().el)
edit: (id) ->
video = #videos.get(id)
#view = new SeehearmeWebapp.Views.Videos.EditView(model: video)
$("#videos").html(#view.render().el)
index_view.js.coffee
SeehearmeWebapp.Views.Videos ||= {}
class SeehearmeWebapp.Views.Videos.IndexView extends Backbone.View
template: JST["backbone/templates/videos/index"]
playerHeight = '360'
playerWidth = '640'
defaultVersion = 0
playlist = []
initialize: () ->
#options.videos.bind('reset', #addAll)
playlist = #options.playlist
addAll: () =>
#options.videos.each(#addOne)
addOne: (video) =>
view = new SeehearmeWebapp.Views.Videos.VideoView({model : video})
#$("tbody").append(view.render().el)
render: =>
$(#el).html(#template(videos: #options.videos.toJSON() ))
#addAll()
$(document).ready ->
player = jwplayer('my-video')
player.setup({playlist: playlist, width: playerWidth, height: playerHeight, skin: "/jwplayer/skins/six/six.xml"})
return this
index.jst.ejs
<div class="video-container">
<div class="row gender-toggle-row">
<div class="gender-toggle-label span1">Show Me: </div>
<div class="btn-group gender-dropdown-group">
<button class="btn btn-extra-large gender-dropdown gender-dropdown-display">Women and Men</button>
<button class="btn btn-extra-large dropdown-toggle gender-dropdown" data-toggle="dropdown">
<img src="assets/down-arrow.png" />
</button>
<ul class="dropdown-menu gender-dropdown-menu">
<li><a class="gender-toggle-element" href="#">Women</a></li>
<li><a class="gender-toggle-element" href="#">Men</a></li>
<li><a class="gender-toggle-element" href="#">Women and Men</a></li>
</ul>
</div>
</div>
<div class="cinema-ribbon-sides"></div>
<div class="cinema-ribbon"><h2 id="question-text" class="white centered-text">seehear.me</h2></div>
<div id='my-video'></div>
<div class="cinema-shadow"></div>
<div class="cinema-controls">
<a id="prev-video" class="cinema-prev"></a>
<div class="meet-btn-text">meet her</div>
<a id="next-video" class="cinema-next"></a>
</div>
</div>
<br />
show_view.js.coffee
SeehearmeWebapp.Views.Videos ||= {}
class SeehearmeWebapp.Views.Videos.ShowView extends Backbone.View
template: JST["backbone/templates/videos/show"]
render: ->
$(#el).html(#template(#model.toJSON() ))
return this
show_view.js.coffee
<h1>THIS IS A VIDEO</h1>
Back
Thanks!

When you render a Backbone view and add it to the page in this way:
$("#videos").html(#view.render().el)
You are rendering to a DOM element that is not yet added to the page. Since you are trying to setup the jwplayer in render(), jwplayer probably can't find the element.
If you add the view to the page first, then render, it might work.
$("#videos").html(#view.el)
#view.render()
Now when you render your template, it is actually on the page before jwplayer tries to find that element.

Related

Add Facebook Pixel specific page in VUE SPA

I have issue for adding specific facebook pixel for SPA Aplication in vue
<script>
!(function(f, b, e, v, n, t, s) {
if (f.fbq) return;
n = f.fbq = function() {
n.callMethod
? n.callMethod.apply(n, arguments)
: n.queue.push(arguments);
};
if (!f._fbq) f._fbq = n;
n.push = n;
n.loaded = !0;
n.version = '2.0';
n.queue = [];
t = b.createElement(e);
t.async = !0;
t.src = v;
s = b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t, s);
})(
window,
document,
'script',
'https://connect.facebook.net/en_US/fbevents.js'
);
fbq('init', 'test');
fbq('track', 'PageView');
fbq('track', 'CompleteRegistration');
</script>
<noscript>
<img
height="1"
width="1"
src="https://www.facebook.com/tr?id=1234456789&ev=PageView
&noscript=1"
/>
</noscript>
<!-- End Facebook Pixel Code -->
i want to add this to my page domainname/register
how to add script head and no script head for specific page in Single Page Application VUE
Step 1: Add facebook pixel code in your public/index.html inside <head> tag
<!-- Facebook Pixel Code -->
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '266xxxxxxxx1');
fbq('track', 'PageView');
</script>
<noscript><img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id=266xxxxxxxx1&ev=PageView&noscript=1"/>
</noscript>
<!-- End Facebook Pixel Code -->
Step 2: Inside your component computed, methods, created you can do,
Inside methods you can do,
methods: {
addtoCart() {
window.fbq('track', 'Add to your shopping cart')
}
}
Inside computed you can do like
computed: {
formattedRating () {
window.fbq('track', 'Format your rating')
return this.fixedPoints === null
? this.currentRating
: this.currentRating.toFixed(this.fixedPoints)
},
}
Inside created you can do like,
created() {
this.loadPressRelease()
window.fbq('track', 'Load press release')
},
You can call the facebook pixel event faq function using window.faq in any components
You can render this script based on the current route name.
First this has to be inside the mounted Vue app (Within <router-view/>) ex: inside a layout component if you have it.
Important, adding the script directly inside the Vue app could cause problems.
Therefore, better to move your script to a js file in your assets folder ex:
/js/fbq.js.
After that check the current route to render it like this:
<!-- Render based on route -->
<component v-if="$route.name == 'register'" :is="`script`" src="/js/fbq.js" async></component>

Trouble to find element depending from other element in testcafe page object model

Having problems implementing the locator lookup method depending on its parent in POM
Example of DOM (roughly):
<div class="lessons">
<div [data-test="lesson"]>
<div class="lesson__info">
<div ...>
<h2 [data-test="lessonTitle"]>FirstLesson</h2>
<div class"lesson__data">
<div [data-test="lessonDataButton"]>
<div class"lesson__controls">
<div [data-test="lessonStartButton"]>
<div [data-test="lesson"]>
<div class="lesson__info">
<div ...>
<h2 [data-test="lessonTitle"]>SecondLesson</h2>
<div class"lesson__data">
<div [data-test="lessonDataButton"]>
<div class"lesson__controls">
<div [data-test="lessonStartButton"]>
Example of my POM:
import { Selector, t } from 'testcafe'
class Page {
constructor() {
this.lesson = Selector('[data-test="lesson"]')
this.lessonDataBtn = Selector('[data-test="lessonDataButton"]')
this.lessonStartBtn = Selector('[data-test="lessonStartButton"]')
this.lessonTitle = Selector('[data-test="lessonTitle"]')
}
async getLessonButton(title, lessonButton) {
const titleLocator = this.lessonTitle.withText(title);
const currentLesson = this.lesson.filter((node) => {
return node.contains(titleLocator())
}, { titleLocator });
const buttonSelector = currentLesson.find((node) => {
return node === lessonButton();
}, { lessonButton });
return buttonSelector;
}
In my test I'm trying to click "lessonDataButton" in specific lesson filtered by its "title":
await t.click(await schedule.getLessonButton(testData.lesson.data.title, page.lessonDataBtn))
It works correctly only for first occurrence of "lessonDataBtn" on page, but if I try to find the same button in second lesson - it will be an error:
The specified selector does not match any element in the DOM tree.
> | Selector('[data-test="lesson"]')
| .filter([function])
| .find([function])
I created an example using the code samples you provided and got a different error:
1. The specified selector does not match any element in the DOM tree.
| Selector('[data-test="lesson"]')
| .filter([function])
> | .find([function])
But I believe the case is the same: the lessonButton() call in the filter function of the find method of the currentLesson selector will always return the first node of the set. A straightforward solution is to search for the button directly with the css selector: const buttonSelector = currentLesson.find('[data-test="lessonDataButton"]');. You also can get rid of filter functions completely:
getLessonButton (title) {
return this.lessonTitle.withText(title)
.parent('[data-test="lesson"]')
.find('[data-test="lessonDataButton"]');
}

Vuejs binding to img src only works on component rerender

I got a component that let's the user upload a profile picture with a preview before sending it off to cloudinary.
<template>
<div>
<div
class="image-input"
:style="{ 'background-image': `url(${person.personData.imagePreview})` } "
#click="chooseImage"
>
<span
v-if="!person.personData.imagePreview"
class="placeholder"
>
<i class="el-icon-plus avatar-uploader-icon"></i>
</span>
<input
type="file"
ref="fileInput"
#change="previewImage"
>
</div>
</div>
</template>
The methods to handle the preview:
chooseImage() {
this.$refs.fileInput.click()
},
previewImage(event) {
// Reference to the DOM input element
const input = event.target;
const files = input.files
console.log("File: ", input.files)
if (input.files && input.files[0]) {
this.person.personData.image = files[0];
const reader = new FileReader();
reader.onload = (e) => {
this.person.personData.imagePreview = e.target.result;
}
// Start the reader job - read file as a data url (base64 format)
reader.readAsDataURL(input.files[0]);
}
},
This works fine, except for when the user fetches a previous project from the DB. this.person.personData.imagePreview get's set to something like https://res.cloudinary.com/resumecloud/image/upload/.....id.jpg Then when the user wants to change his profile picture, he is able to select a new one from his local file system, and this.person.personData.imagePreview is read again as a data url with base64 format. But the preview doesn't work. Only when I change routes back and forth, the correct image selected by the user is displayed.
Like I said in the comment on my post. Turns out I'm an idiot. When displaying the preview, I used this.person.personData.imagePreview . When a user fetches a project from the DB, I just did this.person.personData = response.data. That works fine, apart from the fact that I had a different name for imagePreview on my backend. So I manually set it on the same load method when fetching from the DB like: this.person.personData.imagePreview = this.loadedPersonData.file. For some reason, that screwed with the reactivity of Vue.

Load partial view through controller on Search button click

I am working on an ASP.NET Core 2.1 MVC app using razor. I have searchQuery.cshtml and a (individually working perfectly) viewQuery.cshtml pages. In my searchQuery page, I let user enter queryId and on clicking "Search" button I want to run the action of ViewQuery that displays the results in viewQuery.cshtml and show the viewQuery below the search button area.
I am not good working with Ajax or so. On Search btn click, I call the viewQuery Get action thru ajax. In the button click, I pass the entered queryId of type int. But, when I load searchQuery page, it throws null exception for passing the queryId. I searched few hous, but didn't get any solution.
searchQuery.cshtml UPDATED
<div>
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.QueryId)
</dt>
<dd>
<input asp-for="QueryId" class="form-control" />
</dd>
</dl>
</div>
<input type="submit" value="Show" />
<!-- CHANGE IN CALL -->
Search
</div>
<div class="modal fade" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
×
<h3 class="modal-title">Query Answer</h3>
</div>
<div class="modal-body" id="myModalBodyDiv">
</div>
<div class="modal-footer">
Ok
</div>
</div>
</div>
</div>
<script>
function ShowResult() {
// Retrieve queryId
var queryId = $("#QueryId").val();
// DisplayS PROPERLY
alert("Entered ID " + queryId);
// TRIED '/query/viewQuery' ALSO
$.ajax({
type: 'GET',
url: '../query/viewQuery',
data: { queryId: queryId },
success: function (response) {
alert(response); // **DISPLAYS [Object: object]**
$("#myModalBodyDiv").html(response);
$('#myModal').modal("show");
}, error: function (response) {
alert("Error: " + response);
}
});
}
</script>
My ViewQuery action in controller UPDATED
[Route("[controller]/viewQuery/{queryId:int}")]
public async Task<IActionResult> ViewQuery(int queryId)
{
// Retrieve Data from api using HttpClient
....
return PartialView("ViewQuery", qVM); // PartialView(qVM); //View(qVM);
}
Search Query Action UPDATED
[Route("searchQuery")] // customer/searchQuery
public IActionResult SearchQuery()
{
return View();
}
Can anyone please help me how do I achieve my goal. Simple - a text box were user enters queryId. A button on click, want to pass the entered queryId, call a GET action on controller and get the response. Finally show the response below the search button. I was just trying with the above modal dialog, I prefer text and not dialog.
Try & isolate the issue.
Instead of using model.QueryId in the searchQuery.cshtml, simply hardcode any reference to "modelid" - that way at least you are eliminating the possibility that Model is null on that page. Then instead of onclick="ShowResult(#Model.QueryId)"> , hard code some known id instead of #Model.QueryId. Then debug to see if your ViewQuery action method id hit. If the method is hit, then you can take it from there.
Also, I noticed that your jquery calls may need to be modified:
Instead of: $('myModalBodyDiv').html(response); it should probably be $('#myModalBodyDiv').html(response); (the "#" is missing ..) - same for $('myModal').
You can use Partial Pages(ViewQuery page) , in your searchQuery page , you could use Ajax to call server side action with parameter ID . On server side , you can query the database with ID and return PartialView with models :
[HttpPost]
public IActionResult Students (StudentFilter filters)
{
List students = Student.GetStudents(filters);
return PartialView("_Students", students);
}
Then in success callback function of Ajax , you can load the html of partial view to related area in page using jQuery :
success: function (result) {
$("#searchResultsGrid").html(result);
}
You can click here and here for code sample if using MVC template . And here is code sample if using Razor Pages .

Knockout viewmodel: retrieving data from .Net Core controller

How do I update the Knockout viewmodel with data from my Razor model? My Razor page is generated from a simple GET Action in a .NET Core MVC project. The simple view is:
#model Birder2.ViewModels.SalesOrderViewModel
#using Newtonsoft.Json
#{ string data = JsonConvert.SerializeObject(Model); }
<script src="~/js/salesorderviewmodel.js"></script>
<script type='text/javascript' src="~/js/knockout-3.4.2.js"></script>
<script type='text/javascript'>
var salesOrderViewModel = new SalesOrderViewModel(#Html.Raw(data));
ko.applyBindings(salesOrderViewModel);
</script>
<p data-bind="text: MessageToClient"></p>
<div>
<div>
<label>Customer Name</label>
<span data-bind="text: CustomerName"></span>
</div>
<div>
<label>PO No.</label>
<span data-bind="text: PONumber"></span>
</div>
I have the following Knockout viewmodel, which is referenced above:
SalesOrderItemViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
The controller is here:
public async Task<IActionResult> Details(int? id)
SalesOrderViewModel salesOrderViewModel = new SalesOrderViewModel()
{
SalesOrderId = salesOrder.SalesOrderId,
CustomerName = salesOrder.CustomerName,
PONumber = salesOrder.PONumber
};
salesOrderViewModel.MessageToClient = "I originated from the viewmodel, rather than the model.";
return View(salesOrderViewModel);
I am very new to Knockout. Unfortunately I cannot find any effective documentation specific to .NET Core. The problem seems to be updating the Knockout viewmodel. The view is rendered with empty data fields.
Can anyone help identify the issue?
Everything looks correct. The only thing you should try is to place your script with ko.applyBindings(salesOrderViewModel) below your html with databinds
Alternatively you can use window.onload:
window.onload = function() {
var salesOrderViewModel = new SalesOrderViewModel(#Html.Raw(data));
ko.applyBindings(salesOrderViewModel);
};