Is it possible to remove the parent node from a Tree using CakePHP Tree Behavior?. Let's say for example I have a node like this:
<Node A>
- child 1 node A
- child 2 node A
- child 3 node A
- <Node B> (which is also a child 4 of Node A)
- child 1 node B
- child 2 node B
Is it possible to get all the chidren of Node A (using the chidren() or any other function of the Tree behavior in cakePHP), but exclude a node which has children from the result (in our case Node B)?
Any idea please?
Thanks in advance
You can but you'll need to get your hands a bit dirty because I don't think the behavior allows anything like this.
The key is that all nodes that do not have children should have a left and right value that are in sequence. You'll need to whip up a query like this:
SELECT * FROM items WHERE left > (parent's left) AND right < (parent's right) AND right = left + 1 AND parent_id = (parent's ID)
That way we're asking that all returned values are children of our parent and that their left and right values are in sequence, which they won't be if a node has children.
Looking at specifications there is no specific method for this, so you must build your own function for that using children() and childCount(). Here's the code template (I don't use Cake PHP):
$children = <call TreeBehavior children() method with $id = id of Node A and $direct = true>;
$children_without_children = array();
foreach ($children as $child) {
if (<call TreeBehavior childCount() method with $id = $child->id and $direct = true> === 0) {
$children_without_children[] = $child;
}
}
Then $children_without_children should contain what you want.
you can use this code:
$this->Node->removeFromTree($id, true);
here is code from my cakephp 2.x project:
public function delete($id = null) {
$this->ProductCategory->id = $id;
if (!$this->ProductCategory->exists()) {
throw new NotFoundException(__('Invalid product category'));
}
$this->request->allowMethod('post', 'delete');
if ($this->ProductCategory->removeFromTree($id, TRUE)) {
$this->Session->setFlash(__('The product category has been deleted.'));
} else {
$this->Session->setFlash(__('The product category could not be deleted. Please, try again.'));
}
return $this->redirect(array('action' => 'index'));
}
Using this method (e.i. removeFromTree ()) will either delete or move a node but retain its sub-tree, which will be reparented one level higher. It offers more control than delete, which for a model using the tree behavior will remove the specified node and all of its children.
Related
How do i get a b c d e from the below xml markup...
<api:field name="test">
<api:text>a</api:text>
<api:text>b</api:text>
<api:text>c</api:text>
<api:text>d</api:text>
<api:text>e</api:text>
</api:field>
I am trying to use this for loop:
foreach ($xml->xpath('//api:field[#name="test"]') as $item)
{
foreach ($item->children() as $child) {
...
}
}
but i do not know how to access the child nodes which contain no attributes.
I need to get the child values for the parent node "test" specifically so please don't give me $xml->xpath("//api:text"); as an answer. The problem with this answer is that we might see under other parent nodes and i only want to get the child values from a specific parent node. In this case name="test".
There are a couple of ways you can achieve this. Either just extend your xpath expression to return the child nodes themselves:
foreach ($sxml->xpath('/api:field[#name="test"]/api:text') as $item) {
echo (string) $item, PHP_EOL;
}
Alternatively if you did want to use two loops (or it just better fits your use case), you just need to pass the namespace prefix into the children() method:
foreach ($sxml->xpath('/api:field[#name="test"]') as $item) {
foreach ($item->children('api', true) as $child) {
echo (string) $child, PHP_EOL;
}
}
(If the namespace prefix is likely to change, you can use the registerXPathNamespace method to register a persistent one, according to the defined namespace URL.)
Both of these approaches will yield the same result:
a
b
c
d
e
See https://eval.in/954569 for a full example
how can I show the whole tree. I have a 3 Level Navigation (First is root). With that code, I will only see the 2nd Level.
$tree = \App\Category::where('identifier', 'soccer')->first();
foreach($tree->getDescendants()->toHierarchy() as $descendant) {
echo "{$descendant->name} <br>";
}
You can get the whole tree including root by doing:
$root = Table::where('id', '=', $id)->first();
$tree = $root->getDescendantsAndSelf()->toHierarchy();
Now as $tree is Tree structure you need to traverse it recursively or with a queue data structure (DFS or BFS). Each item on the tree will have a children property with its children
Some pseudo for traversing would be:
function traverseBFS(tree) {
q = Queue()
q.push(tree[0]);
while (!q.empty()) {
item = q.top(); q.pop();
// do what you need with item
foreach (item->children as child) {
q.push(child);
}
}
}
I currently have a code snippet where for each category, it would find the sub-categories:
$categories = array_map(
function($child)
{
$child['children'] =
$this->getChildren(
$child['id'],
!empty($this->request->get['language_id']) ?
$this->request->get['language_id'] : 1
);
return $child;
}, $categories);
getChildren() would recursively get the children of one category:
private function getChildren($parent_id, $language_id) {
$this->load->model('official/category');
$children =
$this->model_official_category->getCategoriesByParentId(
$parent_id,
$language_id
);
// For each child, find the children.
foreach ($children as $child) {
$child['children'] = $this->getChildren(
$child['id'],
$language_id
);
}
return $children;
}
Currently, using my lambda function within the array_map(), only the sub-category's children would be retrieve, so if each sub-category has its own sub-sub-category, it would not be saved into its children.
How could I show the sub-sub-category given the sub-category we have?
What I wanted to do with my code was to take a parent, get its children, and then treat each of those children as a parent and get its children recursively, however my JSON output does not reflect that. Only the parent has children - the children has no children (despite my database having them).
The problem is that your recursion foreach loop assigns the children that it retrieves to a copy of the child data, rather than the child data itself.
To resolve this you could use foreach loop that references the child data, like so:
foreach ($children as &$child) {
However, due to a number of reasons related to how foreach is implemented internally in PHP (more info if you're interested), it would be considerably more memory efficient to use a for loop instead, as this will avoid quite a few copy-on-write copies of the child data:
for ($i = 0; isset($children[$i]); $i++) {
$children[$i]['children'] = $this->getChildren(
$children[$i]['id'],
$language_id
);
}
This is one place where using objects instead of arrays to represent the child data might be a good idea, because objects are always passed by reference (kind of) and the behaviour would be more like what you were expecting initially.
Trying to get child of a specific category which is active. Please help. I am having trouble doing it. I'm currently able to show them all but not specifically. Would appreciate any help.
$category = Mage::getModel('catalog/category')->load(2);
$category->getChildCategories();
$tree = $category->getTreeModel();
$tree->load();
$ids = $tree->getCollection()->getAllIds();
here is code to load active category
/* Load category by id*/
$cat = Mage::getModel('catalog/category')->load($id);
/*Returns comma separated ids*/
$subcats = $cat->getChildren();
//Print out categories string
#print_r($subcats);
foreach(explode(',',$subcats) as $subCatid)
{
$_category = Mage::getModel('catalog/category')->load($subCatid);
if($_category->getIsActive())
{
$caturl = $_category->getURL();
$catname = $_category->getName();
if($_category->getImageUrl())
{
$catimg = $_category->getImageUrl();
}
echo '<h2><img src="'.$catimg.'" alt="" />'.$catname.'</h2>';
}
}
?>
hope this is sure help you.
As mentioned by mhaupt, it is faster to load a collection rather than each category in a loop. But, as far as I am concerned, there is no need to manually load the child categories. Basically this is what $category->getChildrenCategories() already does.
There is also a filter to get active categories only. Just call addIsActiveFilter() on the collection.
a.) Load active child categories via getChildren()
// 1. Get a list of all child category ids (e.g "12,23,11,42")
$subcategoryIds = $category->getChildren();
// 2. Create collection
$categoryCollection = Mage::getModel('catalog/category')->getCollection();
// 3. Add all attributes to select, otherwise you can not
// access things like $cat->getName() etc.
$categoryCollection->addAttributeToSelect('*');
// 4. Filter by ids
$categoryCollection->addIdFilter($subcategoryIds);
// 5. Add filter to collection to get active categories only
$categoryCollection->addIsActiveFilter();
b.) Load active child categories with getChildrenCategories()
// 1. Load collection
$categoryCollection= $category->getChildrenCategories();
// 2. Add filter to collection to get active categories only
$categoryCollection->addIsActiveFilter();
The collection will be loaded form the database as soon as it is accessed. If the collection is not loaded and $subcategories->count() is called only a "SELECT count(*)" will be fired against the database (in contrast to count($subcategories) which will force the collection to load itself).
Iterating the collection
foreach($categoryCollection as $category) {
echo $category->getName();
}
If you add more filters to the collection after accessing it, the collection will not load itself again automatically. To apply changes to the collection, just call $categoryCollection->load() to reload the collection from the database.
Those who are saying to use getAllChildren() instead of getChildren() are simply wrong.
Both methods return the exact same thing, with one difference, getAllChildren(true) will return an array instead of a comma delimited string. getAllChildren($bool asArray) defaults to false. My point being that either way you're going to have to use
Mage::getModel('catalog/category')->load($catId);
inside of a loop unless you use the function below.
private function fetchCatsById($onlyThese)
{
$cats = Mage::getModel('catalog/category')
->getCollection(true)
->addAttributeToSelect('*')
->addIdFilter($onlyThese)
->addAttributeToFilter('level','2')
->addIsActiveFilter();
return $cats;
}
$cats = $this->fetchCatsById($onlyThese);
The one answer liyakat wrote, should not be used in professional shops, because it raises a performance issue, because of the multiple n time loads of the category object, rather use the collection of categories for that, get all children
$cat->getAllChildren()
, then limit the category collection by the needed category ids like
$coll->addIdFilter($idFilter);
then you won't have to load n times against the database.
Please do keep in mind that loads within loops are one of the most often used bad code examples in any Magento projects and to avoid them!
Hello you will see below code
$category_model = Mage::getModel('catalog/category');
$_category = $category_model->load(13);
$all_child_categories = $category_model->getResource()->getAllChildren($_category);
print_r($all_child_categories);
If you want any number of subcategories of parent category than Click here http://magentoo.blogspot.com/2014/01/get-all-subcategories-of-parent-category-magento.html
Im doing a project in php CodeIgniter which has a table where all attributes_values can be kept and it is designed such that it can have its child in same tbl. the database structure is
fld_id fld_value fld_attribute_id fld_parent_id
1 att-1 2 0
2 att-2 2 0
3 att-1_1 2 1
4 att-1_2 2 1
5 att-1_1_1 2 3
here above att-1 is the attribute value of any attribute and it has two child att-1_1 and att-1_2 with parent id 1. and att-1_1 has too its child att-1_1_1 with parent_id 3. fld_parent_id is the fld_id of the same table and denotes the child of its. Now i want to show this in tree structure like this
Level1 level2 level3 ..... level n
att-1
+------att-1_1
| +------att-1_1_1
+------att-1_2
att-2
and this tree structure can vary upto n level. the attribute values with parent id are on level one and i extracted the values from level one now i have to check the child of its and if it has further child and display its child as above. i used a helper and tired to make it recursive but it didnt happen. So how could i do it such: the code is below
foreach($attributes_values->result() as $attribute_values){
if($attribute_values->fld_parent_id==0 && $attribute_values->fld_attribute_id==$attribute->fld_id){
echo $attribute_values->fld_value.'<br/>';
$children = get_children_by_par_id($attribute_values->fld_id); //helper function
echo '<pre>';
print_r($children);
echo '</pre>';
}
}
and the helper code is below:
function get_children_by_par_id($id){ //parent id
$children = get_children($id);
if($children->num_rows()!=0){
foreach($children->result() as $child){
get_children_by_par_id($child->fld_id);
return $child;
}
}
}
function get_children($id){
$CI = get_instance();
$CI->db->where('fld_parent_id',$id);
return $CI->db->get('tbl_attribute_values');
}
please help me...............
The key of recursion is an "endless" call. This can be done with a function that calls it self.
So
function get_children($parent_id)
{
// database retrieve all stuff with the parent id.
$children = Array();
foreach($results as $result)
{
$result['children'] = get_children($result['id']);
$children[] = $result;
}
return $children;
}
Or use the SPL library built into PHP already PHP recursive iterator