I have to develop a very large platform and I need some improvements in some plugins.
Basically, I have a template which use smarty as engine (it doesn't matter this) and I have this code in that template:
<div class="imageLoader">
<div id="main_picture" data-instance="article" data-location="{$smarty.session.CONFIG.DIR.C_PHOTOS_DIR}">
{if $data.main_picture}
<input type='hidden' name='main_picture' value="{$data.main_picture}" />
<img src="{$smarty.session.CONFIG.DIR.C_PHOTOS_DIR}{$data.main_picture}" />
<span class="NTPDelete" onclick="javascript: ntpDeleteImage(this);">
{$smarty.session.language.ntp.delete}
</span>
{else}
<span class="NTPOpenLoader" onclick='javascript: ntpOpenLoader(this);'>
{$smarty.session.language.ntp.add}
</span>
{/if}
</div>
</div>
I have also a js script which contains this code:
function ntpDeleteImage(elem) {
var parent = $(elem).parent();
var szInstanceName = $(parent).attr('data-instance');
var params = {
'filename' : encodeURI($(parent).find('input').val()),
'instance': szInstanceName
};
$.post("./ajax/ntp.delete.php", params, function(data){
})
.done(function(data){
$(parent).find('img, span').remove();
$(parent).find('input').val('');
$("<span />", {
class: 'NTPOpenLoader'
}).html(ntpAddPictureText).appendTo(parent);
$(parent).on('click', 'span.NTPOpenLoader', function(){
ntpOpenLoader(elem);
});
});
}
function ntpOpenLoader(elem){
var parent = $(elem).parent();
var szInstanceName = $(parent).attr('data-instance');
console.log(parent);
window.open("ntp.loader.php?id=" + parent[0].id + "&instance=" + szInstanceName, "_blank", "width=400,height=170,top="+event.clientY+",left="+(event.clientX-150));
}
Method ntpOpenLoader() have two contexts: first is the method ntpDeleteImage and the second is directly from template code (see template code above).
When I run ntpOpenLoader() directly from template code it works fine.
When I run ntpOpenLoader() from ntpDeleteImage() context my span dom doesn't see the parent. Actually, I don't think that my span (with NTPOpenLoader class) retrieve correctly the parent.
Debugging this from Chrome I have in console as follow:
In (template context) console return.
[div#main_picture, prevObject: n.fn.init[1], context: span.NTPOpenLoader]
In context of running from ntpDeleteImage I have:
[prevObject: n.fn.init[1], context: span.NTPOpenLoader]
This means I have an object without parent.
Please help me find my error and also correct me where I'm wrong.
I'm was totally idiot to forgot the context of elem inside of onclick event binding. The correct call is ntpOpenLoader(this). Now it's work perfectly.
Thanks folks!
Related
What I've done is loaded some HTML from a file and I am attempting to modify some elements within that HTML.
The initialization looks like this:
var id = player_info["ID"];
$("#main_container").append(
$("<div />").attr({class: "player_container", id: "player_" + id}).css("display", "none")
);
// Add all information to the player container
var player_container = $("#player_" + id);
player_container.load("player_layout.html");
With player_layout.html looking like this:
<div class="player_name">
</div>
<div class="player_chips">
Chips:
<br/>
<span class='bidding'></span>/<span class='chips'></span>
</div>
<div class="player_stats">
Wins / Losses
<br/>
<span class="wins"></span>/<span class="losses"></span>(<span class="total_games"></span>)
<br/><br/>
Chips Won / Chips Lost
<br/>
<span class="chips_won"></span>/<span class="chips_lost"></span>
</div>
<button class="player_won">Player Has Won</button>
I then want to modify some of the elements, specifically classes. An example of the way I was initially doing this is:
player_container.find(".player_name").text(player_info['username']);
This wasn't working so I then tried to switch find with children and text with html but that didn't seem to work. I then tried this:
$('> .player_name', player_container).html(player_info['username']);
but that also didn't work. I understand that I can use DOM to grab the childNodes and compare the class names but there are a lot of classes that need modifying and I'd also like to know if this is possible in JQuery. Thanks in advance for any help.
You need to use complete callback method of .load()
var player_container = $("#player_" + id);
player_container.load("player_layout.html", function(){
player_container.find(".player_name").text(player_info['username']);
});
I'm pretty experienced with Knockout but this is my first time using components so I'm really hoping I'm missing something obvious! I'll try and simplify my use case a little to explain my issue.
I have a HTML and JS file called Index. Index.html has the data-bind for the component and Index.js has the ko.components.register call.
Index.html
<div data-bind="component: { name: CurrentComponent }"></div>
Index.js
var vm = require("SectionViewModel");
var CurrentComponent = ko.observable("section");
ko.components.register("section", {
viewModel: vm.SectionViewModel,
template: "<h3>Loading...</h3>"
});
ko.applyBindings();
I then have another HTML and JS file - Section.html and SectionViewModel.js. As you can see above, SectionViewModel is what I specify as the view model for the component.
Section.html
<div>
<span data-bind="text: Section().Name"></span>
</div>
SectionViewModel.js
var SectionViewModel = (function() {
function SectionViewModel() {
this.Section = ko.observable();
$.get("http://apiurl").done(function (data) {
this.Section(new SectionModel(data.Model)); // my data used by the view model
ko.components.get("dashboard", function() {
component.template[0] = data.View; // my html from the api
});
});
}
return SectionViewModel;
});
exports.SectionViewModel = SectionViewModel;
As part of the constructor in SectionViewModel, I make a call to my API to get all the data needed to populate my view model. This API call also returns the HTML I need to use in my template (which is basically being read from Section.html).
Obviously this constructor isn't called until I've called applyBindings, so when I get into the success handler for my API call, the template on my component is already set to my default text.
What I need to know is, is it possible for me to update this template? I've tried the following in my success handler as shown above:
ko.components.get("section", function(component) {
component.template[0] = dataFromApi.Html;
});
This does indeed replace my default text with the html returned from my API (as seen in debug tools), but this update isn't reflected in the browser.
So, basically after all that, all I'm really asking is, is there a way to update the content of your components template after binding?
I know an option to solve the above you might think of is to require the template, but I've really simplified the above and in it's full implementation, I'm not able to do this, hence why the HTML is returned by the API.
Any help greatly appreciated! I do have a working solution currently, but I really don't like the way I've had to structure the JS code to get it working so a solution to the above would be the ideal.
Thanks.
You can use a template binding inside your componente.
The normal use of the template bindign is like this:
<div data-bind="template: { name: tmplName, data: tmplData }"></div>
You can make both tmplData and tmplName observables, so you can update the bound data, and change the template. The tmplName is the id of an element whose content will be used as template. If you use this syntax you need an element with the required id, so, in your succes handler you can use something like jQuery to create a new element with the appropriate id, and then update the tmplname, so that the template content gets updated.
*THIS WILL NOT WORK:
Another option is to use the template binding in a different way:
<div data-bind="template: { nodes: tmplNodes, data: tmplData }"></div>
In this case you can supply directly the nodes to the template. I.e. make a tmplNodes observable, which is initialized with your <h3>Loading...</h3> element. And then change it to hold the nodes received from the server.
because nodesdoesn't support observables:
nodes — directly pass an array of DOM nodes to use as a template. This should be a non-observable array and note that the elements will be removed from their current parent if they have one. This option is ignored if you have also passed a nonempty value for name.
So you need to use the first option: create a new element, add it to the document DOM with a known id, and use that id as the template name. DEMO:
// Simulate service that return HTML
var dynTemplNumber = 0;
var getHtml = function() {
var deferred = $.Deferred();
var html =
'<div class="c"> \
<h3>Dynamic template ' + dynTemplNumber++ + '</h3> \
Name: <span data-bind="text: name"/> \
</div>';
setTimeout(deferred.resolve, 2000, html);
return deferred.promise();
};
var Vm = function() {
self = this;
self.tmplIdx = 0;
self.tmplName = ko.observable('tmplA');
self.tmplData = ko.observable({ name: 'Helmut', surname: 'Kaufmann'});
self.tmplNames = ko.observableArray(['tmplA','tmplB']);
self.loading = ko.observable(false);
self.createNewTemplate = function() {
// simulate AJAX call to service
self.loading(true);
getHtml().then(function(html) {
var tmplName = 'tmpl' + tmplIdx++;
var $new = $('<div>');
$new.attr('id',tmplName);
$new.html(html);
$('#tmplContainer').append($new);
self.tmplNames.push(tmplName);
self.loading(false);
self.tmplName(tmplName);
});
};
return self;
};
ko.applyBindings(Vm(), byName);
div.container { border: solid 1px black; margin: 20px 0;}
div {padding: 5px; }
.a { background-color: #FEE;}
.b { background-color: #EFE;}
.c { background-color: #EEF;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="byName" class="container">
Select template by name:
<select data-bind="{options: tmplNames, value: tmplName}"></select>
<input type="button" value="Add template"
data-bind="click: createNewTemplate"/>
<span data-bind="visible: loading">Loading new template...</span>
<div data-bind="template: {name: tmplName, data: tmplData}"></div>
</div>
<div id="tmplContainer" style="display:none">
<div id="tmplA">
<div class="a">
<h3>Template A</h3>
<span data-bind="text: name"></span> <span data-bind="text: surname"></span>
</div>
</div>
<div id="tmplB">
<div class="b">
<h3>Template B</h3>
Name: <span data-bind="text: name"/>
</div>
</div>
</div>
component.template[0] = $(data)[0]
I know this is old, but I found it trying to do the same, and the approcah helped me come up with this in my case, the template seems to be an element, not just raw html
suppose I have a Meteor template called message, where the client can post messages. the post is wrapped in a div that gets an id equal to its unique id in the Mongo collection.
<template name="message">
<div class="msg comment" id="{{this._id}}">{{msg}}</div>
</template>
is there anyway to reference the specific id in Meteor.message.rendered? right now I am using this._id and it's not working. Here is my function
Template.message.rendered = function() {
texts = $('this._id').html();
texts = texts.replace(/#(\w+)/g,
"<a href='https://www.google.com/?q=$1'target='_blank'>$&</a>");
$("this._id").html(texts);
}
Four things:
You're using a string instead of the variable: $('this._id') -> $(this._id)
The context (this) of the rendered function is a template helper, and not the data itself (which is the context of the template), so replace this._id with this.data._id to match {{ this._id }}
It's an ID selector : $(this.data._id) -> $('#' + this.data._id)
Rendered callbacks run whenever the template is rerendered, and whenever a subtemplate (a template contained within the template) is rendered, so you should flag when it has been rendered (source, interesting article about meteor rendered).
Final code :
Template.message.rendered = function() {
if(!this.rendered) {
this.rendered = true;
texts = $('#' + this.data._id).html();
texts = texts.replace(/#(\w+)/g,"<a href='https://www.google.com/?q=$1'target='_blank'>$&</a>");
$('#' + this.data._id).html(texts);
}
}
I have a grails app where, after a user enters a name for a new domain object (Sync), I want to save the object, move to a fragment on the same page, and change the css class of a div (to js colorbox, if that matters).
To do this, I use an anchor to set the class and move to the fragment and use JS to submit a g:formRemote. However, the formRemote does not return the created object.
partial of gsp:
<g:formRemote url="[controller: 'Main', action:'createNewSync']" name="newSyncForm" >
<g:field type="text" name="newSyncName" />
<a id="ns-link" href="#outline_content" class="outline">
<script>
$('#ns-link').click(function(){
$('#newSyncForm').submit();
});
</script>
</g:formRemote>
Later in the gsp, we want to move to use the colorbox with the outline_content inside. Notice the syncInstance.name is needed.
<script>
$(document).ready(function(){
$(".outline").colorbox({inline:true, width:"1140px", escKey:false, overlayClose:false});
</script>
<div id="sync" class="hidden">
<div id='outline_content' style='padding:10px; background:#fff;' >
<h2 class="nameheader"><strong style="color:#000;">New Sync:</strong><span class="editable_textile">${syncInstance?.name}</span></h2>
<div class="number1"><img src="../images/1.png" border="0" /></div>
.....
controller:
def createNewSync(){
params.name = params.newSyncName
def syncInstance = Sync?.findByName(params.newSyncName)
if (!syncInstance)
{
syncInstance = new Sync(params)
def u = User.findByUsername(springSecurityService.principal)
syncInstance.properties['createdBy'] = u
syncInstance.properties['createdDate'] = new Date().toString()
syncInstance.properties['lastRunTime'] = "Never"
syncInstance.properties['lastRunOutcome'] = "---"
syncInstance.properties['isScheduled'] = false
syncInstance.properties['isComplete'] = false
syncInstance.save(failOnError: true, flush: true)
}
//doesn't send anything back to page if it's been called remotely
[syncInstance: syncInstance]
}
Is there any way to get a reference to the created object to be used later on the page using this method? If not, is there another way to accomplish this?
Ok, so here is what I would do
1) Create a template for the sync. It would be everything contained inside the div with the id of "sync", but not the div itself.
2) Update your formRemote tag to update that div <g:formRemote update="sync" ... />
3) Render the template in your controller render(template: "path/to/template", model:[syncInstance: syncInstance])
<h:commandLink id="#{id}" value="#{value}" action="#{actionBean.getAction}" onclick="alert(this);"/>
In the previous simplified example, the 'this' keyword used in the Javascript function won't reference the generated A HREF element, but will reference to the global window, because JSF generates the following (simplified) code:
<a onclick="var a=function(){alert(this)};var b=function(){if(typeof jsfcljs == 'function'){jsfcljs(document.forms['mainForm'],'generatedId,action,action','');}return false};return (a()==false) ? false : b();" href="#" id="generatedId">text</a>
So because JSF wraps the user defined onclick in a function, this will point to the global window. I don't have access to the id of the element because my code is used in a generic component that can be used in loops etc. and I don't use backing beans (Facelets + JSF).
So my question is, is there a way to access the A HREF element from within my onclick javascript function, without knowing the id?
EDIT:
Go here for a small library/sample code for getting the clientId from the id: JSF: working with component IDs
The HTML ID emitted by JSF controls is namespaced to avoid collisions (e.g. the control is a child of UIData and emitted multiple times, or the container is a portlet so the view can be rendered multiple times in one HTML page). This ID is known as the clientId, and is distinct from the ID set on the JSF control.
If you want to emit the clientId in the view, you can use a managed bean to do it.
public class JavaScriptBean {
public String getOnclickButton1() {
String id = "button1";
String clientId = getClientId(id);
return "alert('You clicked element id=" + clientId + "');";
}
public String getOnclickButton2() {
String id = "button2";
String clientId = getClientId(id);
return clientId;
}
private String getClientId(String id) {
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot view = context.getViewRoot();
UIComponent component = find(view, id);
return component.getClientId(context);
}
private UIComponent find(UIComponent component, String id) {
if (id.equals(component.getId())) {
return component;
}
Iterator<UIComponent> kids = component.getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent kid = kids.next();
UIComponent found = find(kid, id);
if (found == null) {
continue;
}
return found;
}
return null;
}
}
This is configured in the application's faces-config.xml and bound to the view using the expression language:
<f:view>
<h:form>
<h:commandButton id="button1" value="b1"
onclick="#{javaScriptBean.onclickButton1}" />
<h:commandButton id="button2" value="b2"
onclick="alert('You clicked element id=#{javaScriptBean.onclickButton2}');" />
</h:form>
</f:view>
var a=function(){alert(this)};var b=function(){if(typeof jsfcljs == 'function'){jsfcljs(document.forms['mainForm'],'generatedId,action,action','');}return false};return (a()==false) ? false : b();
Oh dear lord! That's violently horrible.
And no, it appears to be throwing away 'this' before you get a chance to look at it - it should have said "return (a.call(this)==false)? false : b()".
On IE only you could read the srcElement from window.event, but that's pretty ugly.
How about avoiding JSF's weird event mangling entirely, by using unobtrusive scripting? eg. omit the onclick and say:
document.getElementById('generatedId').onclick= function() {
alert(this);
return false; // if you want to not follow the link
}
If you need the link to continue into JSF's event handling, you'd have to save the old onclick event handler and call it at the end of the function, or use Element.addEventListener (attachEvent on IE) to put your scripting hooks in at an earlier stage than the onclick.
<head>
..
<script type="text/javascript">
var myCommandLinkId = "undefined";
</script>
..
</head>
..
<h:commandLink id="myCommandLink" value="Click Me" onmousedown="myCommandLinkId=this.id;" onclick="alert(myCommandLinkId);" />