CanJS Live Binding with lazy load of Components Not working - canjs

I'm using headJs to lazy load scripts. For simplicity lets say I have 3 components, layout-component(tag : custom-layout), custom1-component(tag : custom-one) and custom2-component(tag : custom-two).
index.html
<button class="one">One</button>
<button class="two">Two</button>
{{#is layout 'user'}}
<custom-layout page="{state.page}"></custom-layout>
{{/is}}
app.js
var state = new can.Map.extend({page : '', layout: 'user'})
// on some action I can set the page to 'one' or 'two, like so
$('button').on('click', function(e){
if ($(e.target).hasClass('one')) {
head.load(['custom1-component.js'], function(){
state.attr('page', 'one')
})
}
else if ($(e.target).hasClass('two')) {
head.load(['custom2-component.js'], function(){
state.attr('page', 'two')
})
}
})
layout-component.js
can.Component.extent({
tag : 'custom-layout',
tempate : can.view('custom-layout.stache')
})
custom-layout.stache
{{#is page 'one'}}
<custom-one></custom-one>
{{/is}}
{{#is page 'two'}}
<custom-two></custom-two>
{{/is}}
So initially I have set the layout to 'user' for simplicity. On Load the custom-layout is already fetched and stache executed. However at this point there are no custom-one or custom-two components loaded. On click of the button, I load the component defs and change the page value appropriately.
The problem is that the custom-one and custom-two are recognised as can component and do not render.

Related

Encapsulation with React child components

How should one access state (just state, not the React State) of child components in React?
I've built a small React UI. In it, at one point, I have a Component displaying a list of selected options and a button to allow them to be edited. Clicking the button opens a Modal with a bunch of checkboxes in, one for each option. The Modal is it's own React component. The top level component showing the selected options and the button to edit them owns the state, the Modal renders with props instead. Once the Modal is dismissed I want to get the state of the checkboxes to update the state of the parent object. I am doing this by using refs to call a function on the child object 'getSelectedOptions' which returns some JSON for me identifying those options selected. So when the Modal is selected it calls a callback function passed in from the parent which then asks the Modal for the new set of options selected.
Here's a simplified version of my code
OptionsChooser = React.createClass({
//function passed to Modal, called when user "OK's" their new selection
optionsSelected: function() {
var optsSelected = this.refs.modal.getOptionsSelected();
//setState locally and save to server...
},
render: function() {
return (
<UneditableOptions />
<button onClick={this.showModal}>Select options</button>
<div>
<Modal
ref="modal"
options={this.state.options}
optionsSelected={this.optionsSelected}
/>
</div>
);
}
});
Modal = React.createClass({
getOptionsSelected: function() {
return $(React.findDOMNode(this.refs.optionsselector))
.find('input[type="checkbox"]:checked').map(function(i, input){
return {
normalisedName: input.value
};
}
);
},
render: function() {
return (
//Modal with list of checkboxes, dismissing calls optionsSelected function passed in
);
}
});
This keeps the implementation details of the UI of the Modal hidden from the parent, which seems to me to be a good coding practice. I have however been advised that using refs in this manner may be incorrect and I should be passing state around somehow else, or indeed having the parent component access the checkboxes itself. I'm still relatively new to React so was wondering if there is a better approach in this situation?
Yeah, you don't want to use refs like this really. Instead, one way would be to pass a callback to the Modal:
OptionsChooser = React.createClass({
onOptionSelect: function(data) {
},
render: function() {
return <Modal onClose={this.onOptionSelect} />
}
});
Modal = React.createClass({
onClose: function() {
var selectedOptions = this.state.selectedOptions;
this.props.onClose(selectedOptions);
},
render: function() {
return ();
}
});
I.e., the child calls a function that is passed in via props. Also the way you're getting the selected options looks over-fussy. Instead you could have a function that runs when the checkboxes are ticked and store the selections in the Modal state.
Another solution to this problem could be to use the Flux pattern, where your child component fires off an action with data and relays it to a store, which your top-level component would listen to. It's a bit out of scope of this question though.

AngularJS directive toggle menu preventing default for other directive

So I made a directive for a toggle (drop down) menu in AngularJS. I used the directive for multiple items within the page but I have a small problem. When one item is open and I click another one I want the previous one to close. The event.preventDefault and event.stopPropagation stops the event for the previous item and doesn't close it. Any ideas on how to fix this? Is there a way to perhaps only stop the event within the scope?
app.directive('toggleMenu', function ($document) {
return {
restrict: 'CA',
link: function (scope, element, attrs) {
var opened = false;
var button = (attrs.menuButton ? angular.element(document.getElementById(attrs.menuButton)) : element.parent());
var closeButton = (attrs.closeButton ? angular.element(document.getElementById(attrs.closeButton)) : false);
var toggleMenu = function(){
(opened ? element.fadeOut('fast') : element.fadeIn('fast'));
};
button.bind('click', function(event){
event.preventDefault();
event.stopPropagation();
toggleMenu();
opened = ! opened;
});
element.bind('click', function(event){
if(attrs.stayOpen && event.target != closeButton[0]){
event.preventDefault();
event.stopPropagation();
}
});
$document.bind('click', function(){
if(opened){
toggleMenu();
opened = false;
}
});
}
};
And here's a Fiddle: http://jsfiddle.net/JknUJ/5/
Button opens content and content should close when clicked outside the div. When clicked on button 2 however content 1 doesn't close.
Basic idea is that you need to share the state between all your dropdown submenus, so when one of them is shown, all others are hidden. The simpliest way of storing state (such as opened or closed) are... CSS classes!
We'll create a pair of directives - one for menu, and another for sumbenu. It is more expressive that just divs.
Here is out markup.
<menu>
<submenu data-caption="Button 1">
Content 1
</submenu>
<submenu data-caption="Button 2">
Content 2
</submenu>
</menu>
Look how readable is it! Say thanks to directives:
plunker.directive("menu", function(){
return {
restrict : "E",
scope : {},
transclude : true,
replace : true,
template : "<div class='menu' data-ng-transclude></div>",
controller : function ($scope, $element, $attrs, $transclude){
$scope.submenus = [];
this.addSubmenu = function (submenu) {
$scope.submenus.push(submenu);
}
this.closeAllSubmenus = function (doNotTouch){
angular.forEach($scope.submenus, function(submenu){
if(submenu != doNotTouch){
submenu.close();
}
})
}
}
}
});
plunker.directive("submenu", function(){
return {
restrict : "E",
require : "^menu",
scope : {
caption : "#"
},
transclude : true,
replace : true,
template : "<div class='submenu'><label>{{caption}}</label><div class='submenu-content' data-ng-transclude></div></div>",
link : function ($scope, $iElement, $iAttrs, menuController) {
menuController.addSubmenu($scope);
$iElement.bind("click", function(event){
menuController.closeAllSubmenus($scope);
$iElement.toggleClass("active");
});
$scope.close = function (){
$iElement.removeClass("active");
}
}
}
});
Look thar we restricted them to HTML elements (restrict : "E"). submenu requires to be nested in menu (require : "^menu"), this allows us to inject menu controller to submenu's link function. transclude and replace controls the position of original markup in compiled HTML output (replace=true means that original markup will be replaced with compiled, transclude inserts parts of original markup to compiled output).
When we've done with this, we just say to menu close all your child menus! and menu iterates over submenus, forcing them to close.
We are adding childs to menu controller in addSubmenu function. It is called in submenus link function, thus every compiled instance of submenu adds itself to menu. Now, closing all submenus is as easy as iterating over all children, this is done by closeAllSubmenus in menu controller.
Here is a full Plunker to play with.

Kendo splitter control load right panel contents asynchronously

I have a Kendo splitter control with left/right panes. Inside the left pane I have a Kendo panel bar control that builds a navigation menu. Unfortunately I inherited this from another developer that left the company and I'm not familiar with the Kendo control.
It all works, but when the user clicks on a menu item, the entire page refreshes, That's for the birds! I want only the right panel to refresh.
Here's the code for the for the layout page:
<body>
#(Html.Kendo().Splitter().Name("splitter").Panes(panes => {
panes.Add().Size("220px").Collapsible(true).Content(#<text>
#Html.Partial("_Panelbar")
</text>);
panes.Add().Content(#<text>
<section id="main">
#RenderBody()
</section>
</text>);
}))
<script type="text/javascript">
$(document).ready(function () {
$('input[type=text]').addClass('k-textbox');
});
</script>
#RenderSection("scripts", required: false)
</body>
and here's the code for the panel partial view:
#(Html.Kendo().PanelBar().Name("panelbar")
.SelectedIndex(0)
.Items(items => {
items.Add().Text("Corporate").Items(corp =>
{
corp.Add().Text("Vendors").Action("Index", "Vendor");
corp.Add().Text("Materials").Action("Index", "CostMaterials");
corp.Add().Text("Packaging").Action("Index", "CostPackaging");
corp.Add().Text("Reports").Action("UnderConstruction", "Home", new { pageTitle = "Reports" });
});
}))
I tried replacing the .Action method on the PanelBar with LoadContentsFrom method. That replaced the content in the left pane. So I guess my question is, how do I target the right side of the splitter control?
Any help would be appreciated.
Thanks
-Alex
Your code maybe like this:
#(Html.Kendo().PanelBar().Name("panelbar")
.SelectedIndex(0)
.Items(items => {
items.Add().Text("Corporate").Items(corp =>
{
corp.Add().Text("Vendors").Url("javascript:void(0);")
.HtmlAttributes(
new {
#class= "helloWorld",
#data-href="/Vendor/Index"
});
});
}))
<script>
$document.ready(function(){
$('.helloWorld').click(function(){
var href = $(this).attr('data-href');
$('#main').load(href);
});
});
</script
UPDATE
There is one thing very important: I think the view /Vendor/Index have the same template with your current page.
It means that when you load /Vendor/Index into the right side. The right side will include entire content (include left panel again).
Solution
You have to create a new view(a template) , which just include your left menu,banner,...
Then, You have to remove all template of other views (which will be loaded into right side - /Vendor/Index , /CostMaterials/Index,...)
2.This way is not a good approach. But I think It will work.
//Reference : Use Jquery Selectors on $.AJAX loaded HTML?
<script>
$document.ready(function(){
$('.helloWorld').click(function(){
var href = $(this).attr('data-href');
$.ajax({
type: 'GET',
url: href,
success: function (data){
var rHtml = $('<html />').html(data);
$('#main').html(rHtml.find('#main'));
}
});
});
});
</script

Ajax from telerik mvc treeviewitem

I am trying to work with telerik MVC TreeView. i have three levels in the treeview. On clicking on the 3rd level tree view item, i need to load a view without page refresh.
I tried to use item.Url. But the entire page refreshes and treeview disappears.
I have a treeview in "Menu" partial view and it is called by _layout.cshtml
#(Html.Telerik().TreeView()
.Name("TreeView")
.ShowLines(false)
.BindTo(Model, mappings =>
{
mappings.For<AdminTool.Web.Models.ProjectModel>(binding => binding
.ItemDataBound((item, project) =>
{
item.Text = project.Name;
})
.Children(project => project.ApiModels));
mappings.For<AdminTool.Web.Models.ApiModel>(binding => binding
.ItemDataBound((item, api) =>
{
item.Text = api.Name;
item.Value = api.Id;
})
.Children(api => api.ApiMethods));
mappings.For<AdminTool.Web.Models.ApiMethodModel>(binding => binding
.ItemDataBound((item, apimethod) =>
{
item.Text = apimethod.Name;
item.Url = Url.Action("ApiMethodById", "ApiMethod", new { id= apimethod.Id });
}));
})
)
Thanks in advance!
I would recommend using jQuery for this. What you would do it the following:
1) Have whatever location you want this to load to be placed inside a tag with some id on it (e.g. 'myDiv')
2) Write a jQuery function that on the click of you third level item (e.g. apiMethodID) to run a function that will call the controller action that you're looking for, and to post that to myDiv. You might want to try something like the following:
$(document).ready(function()){
$('#apiMethodID').click(function(){
var value = $('#apiMethodID').val();
var url = "/ApiMethod/ApiMethodById";
$.get(url, { apiMethodId: value }, function(data){
$('#myDiv').html(data);
});
})
}
$.get here will actually call your controller method. You should be able to debug that fact to make sure that it works.
3) Make sure that the controller action that you going to returns a PartialView, otherwise you're going to re-render the whole page inside of that div.
Let me know if that works.

Load content into expandable DIV with ajax and jquery

I am wanting to use Collapsible DIVs to show and hide content from the user.
I found this jQuery code to do the expand and collapse:
http://webcloud.se/code/jQuery-Collapse/
However the content is already loaded in the divs (its just hidden from view).
So I then found this:
http://www.raymondcamden.com/index.cfm/2011/4/5/Collapsible-content-and-Ajax-loading-with-jQuery-Mobile
Which loads the content into the opening div but also unloads it when it closes!
However its all mixed in with jQuery mobile and so it styled.
I want to be able to style the divs myself. also the first example uses nice bounce or fade effects to bring the content into view.
The reason for doing this is I want to show the user different content such as images or flash files but I don't want everything to load into the page on page load, this would be too much stuff.
So how can I use the first jQuery Collapse example but with loading external pages in?
I liked the question so I spent a little time making something close to a plugin:
//only run the event handler for collapsible widgets with the "data-url" attribute
$(document).delegate('.ui-collapsible[data-url] > .ui-collapsible-heading', 'click', function () {
//cache the collapsible content area for later use
var $this = $(this).siblings('.ui-collapsible-content');
//check if this widget has been initialized yet
if (typeof $this.data('state') === 'undefined') {
//initialize this widget
//update icon to gear to show loading (best icon in the set...)
$this.siblings('.ui-collapsible-heading').find('.ui-icon').removeClass('ui-icon-plus').addClass('ui-icon-gear')
//create AJAX request for data, in this case I'm using JSONP for cross-domain abilities
$.ajax({
//use the URL specified as a data-attribute on the widget
url : $this.closest('.ui-collapsible').data('url'),
type : 'get',
dataType : 'jsonp',
success : function (response) {
//get the height of the new content so we can animate it into view later
var $testEle = $('<div style="position:absolute;left:-9999px;">' + response.copy + '</div>');
$('body').append($testEle);
var calcHeight = $testEle.height();
//remove the test element
$testEle.remove();
//get data to store for this widget, also set state
$this.data({
state : 'expanded',
height : calcHeight,
paddingTop : 10,
paddingBottom : 10
//add the new content to the widget and update it's css to get ready for being animated into view
}).html('<p>' + response.copy + '</p>').css({
height : 0,
opacity : 0,
paddingTop : 0,
paddingBottom : 0,
overflow : 'hidden',
display : 'block'
//now animate the new content into view
}).animate({
height : calcHeight,
opacity : 1,
paddingTop : $this.data('paddingTop'),
paddingBottom : $this.data('paddingBottom')
}, 500);
//re-update icon to minus
$this.siblings('.ui-collapsible-heading').find('.ui-icon').addClass('ui-icon-minus').removeClass('ui-icon-gear')
},
//don't forget to handle errors, in this case I'm just outputting the textual message that jQuery outputs for AJAX errors
error : function (a, b, c) { console.log(b); }
});
} else {
//the widget has already been initialized, so now decide whether to open or close it
if ($this.data('state') === 'expanded') {
//update state and animate out of view
$this.data('state', 'collapsed').animate({
height : 0,
opacity : 0,
paddingTop : 0,
paddingBottom : 0
}, 500);
} else {
//update state and animate into view
$this.data('state', 'expanded').animate({
height : $this.data('height'),
opacity : 1,
paddingTop : $this.data('paddingTop'),
paddingBottom : $this.data('paddingBottom')
}, 500);
}
}
//always return false to handle opening/closing the widget by ourselves
return false;
});​
The collapsible HTML looks like this:
<div data-role="collapsible" data-url="http://www.my-domain.com/jsonp.php">
<h3>Click Me</h3>
<p></p>
</div>
Here is a demo: http://jsfiddle.net/YQ43B/6/
Note that for the demo I found the best way to make the initial animation smooth was to add this CSS:
.ui-mobile .ui-page .ui-collapsible .ui-collapsible-content {
padding-top : 0;
padding-bottom : 0;
}​
The default padding added by jQuery Mobile is 10px for both top and bottom paddings, I added those values as data-attributes to each widget to maintain the defaults.
Note that this code can be slightly tweaked to show other types of content, I used JSONP simply because you can use it on JSFiddle.
Here is an example that I made :
$(document).ready(function () {
$('.accordian_body').hide();
});
$('.accordian_head').click(function () {
$(this).next().animate(
{ 'height': 'toggle' }, 'fast'
);
Then in HTML
<div class="accordian_head"></div>
<div class="accordian_body"></div>
in CSS you can style the head and body however you like , to add in code behind -
<asp:Literal ID="lit1" runat="server" />
foreach(DataRow dr in DataTable.Rows)
{
lit1.Text = lit1.Text + "<div class=\"accordian_head\">" Whatever you want... "</div><div class=\"accordian_body\">" Whatever in body "</div>
}
Check this link it's in php and shows how to load external link to DIV Ajax can load only internal pages and doesn't work across domains.

Resources